f8eb43ac1fea049f52a5293ba49f06f900484b44
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / ContinuousChargingBehavior.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium.charging
37
38 import gr.grnet.aquarium.Aquarium
39 import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
40 import gr.grnet.aquarium.charging.wallet.WalletEntry
41 import gr.grnet.aquarium.computation.BillingMonthInfo
42 import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel}
43 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
44 import gr.grnet.aquarium.policy.{FullPriceTable, ResourceType}
45 import gr.grnet.aquarium.util.LogHelpers.Debug
46 import scala.collection.mutable
47 import gr.grnet.aquarium.event.model.EventModel
48
49 /**
50  * In practice a resource usage will be charged for the total amount of usage
51  * between resource usage changes.
52  *
53  * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
54  *
55  * @author Christos KK Loverdos <loverdos@gmail.com>
56  */
57 final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
58
59   def computeCreditsToSubtract(
60       workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
61       oldCredits: Double,
62       timeDeltaMillis: Long,
63       unitPrice: Double
64   ): (Double /* credits */, String /* explanation */) = {
65
66     val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
67     val credits = HrsOfMillis(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
68     val explanation = "Hours(%s) * MBs(%s) * UnitPrice(%s)".format(
69       HrsOfMillis(timeDeltaMillis),
70       MBsOfBytes(oldAccumulatingAmount),
71       unitPrice
72     )
73
74     (credits, explanation)
75   }
76
77   def computeSelectorPath(
78       workingChargingBehaviorDetails: mutable.Map[String, Any],
79       workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
80       currentResourceEvent: ResourceEventModel,
81       referenceTimeslot: Timeslot,
82       totalCredits: Double
83   ): List[String] = {
84     List(FullPriceTable.DefaultSelectorKey)
85   }
86
87   def initialChargingDetails: Map[String, Any] = Map()
88
89   def computeNewAccumulatingAmount(
90       workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
91       eventDetails: Map[String, String]
92   ): Double = {
93     workingResourceInstanceChargingState.oldAccumulatingAmount +
94     workingResourceInstanceChargingState.currentValue
95   }
96
97   def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
98     val details = resourceEvent.details
99     val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
100
101     resourceEvent.withDetails(newDetails, newOccurredMillis)
102   }
103
104   override def processResourceEvent(
105        aquarium: Aquarium,
106        resourceEvent: ResourceEventModel,
107        resourceType: ResourceType,
108        billingMonthInfo: BillingMonthInfo,
109        workingResourcesChargingState: WorkingResourcesChargingState,
110        userAgreements: AgreementHistoryModel,
111        totalCredits: Double,
112        walletEntryRecorder: WalletEntry ⇒ Unit
113    ): (Int, Double) = {
114
115     // 1. Ensure proper initial state per resource and per instance
116     ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
117
118     // 2. Fill in data from the new event
119     val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
120     val workingResourcesChargingStateDetails = workingResourcesChargingState.details
121     val instanceID = resourceEvent.instanceID
122     val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
123     fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
124
125     val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match {
126       case Some(previousEvent) ⇒
127         Debug(logger, "I have previous event %s", previousEvent.toDebugString)
128         previousEvent
129
130
131       case None ⇒
132         // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
133         // Let's see if we can create a dummy previous event.
134         Debug(logger, "First event of its kind %s", resourceEvent.toDebugString)
135
136         val dummyFirstEventDetails = Map(
137             ResourceEventModel.Names.details_aquarium_is_synthetic   -> "true",
138             ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
139             ResourceEventModel.Names.details_aquarium_reference_event_id -> resourceEvent.id,
140             ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> resourceEvent.stringIDInStoreOrEmpty
141         )
142
143         val dummyFirstEventValue = 0.0 // TODO From configuration
144         val dummyFirstEvent = resourceEvent.withDetailsAndValue(
145             dummyFirstEventDetails,
146             dummyFirstEventValue,
147             billingMonthInfo.monthStartMillis // TODO max(billingMonthInfo.monthStartMillis, userAgreementModel.validFromMillis)
148         )
149
150         Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString)
151
152         dummyFirstEvent
153     }
154
155     val retval = computeWalletEntriesForNewEvent(
156       resourceEvent,
157       resourceType,
158       billingMonthInfo,
159       totalCredits,
160       Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis),
161       userAgreements.agreementByTimeslot,
162       workingResourcesChargingStateDetails,
163       workingResourceInstanceChargingState,
164       aquarium.policyStore,
165       walletEntryRecorder
166     )
167
168     // We need just one previous event, so we update it
169     workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent)
170
171     retval
172   }
173
174   def createVirtualEventsForRealtimeComputation(
175       userID: String,
176       resourceTypeName: String,
177       resourceInstanceID: String,
178       eventOccurredMillis: Long,
179       workingResourceInstanceChargingState: WorkingResourceInstanceChargingState
180   ): List[ResourceEventModel] = {
181     StdResourceEvent(
182       ChargingBehavior.VirtualEventsIDGen.nextUID(),
183       eventOccurredMillis,
184       eventOccurredMillis,
185       userID,
186       "aquarium",
187       resourceTypeName,
188       resourceInstanceID,
189       0.0,
190       EventModel.EventVersion_1_0,
191       Map(
192         ResourceEventModel.Names.details_aquarium_is_synthetic   -> "true",
193         ResourceEventModel.Names.details_aquarium_is_realtime_virtual -> "true"
194       )
195     ) :: Nil
196   }
197 }
198
199 object ContinuousChargingBehavior {
200   private[this] final val TheOne = new ContinuousChargingBehavior
201
202   def apply(): ContinuousChargingBehavior = TheOne
203 }