Several fixes here and there => Removed all exceptions caused by Avro. Rolled back...
[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.charging.state.UserStateModel
39 import gr.grnet.aquarium.computation.BillingMonthInfo
40 import gr.grnet.aquarium.event.{CreditsModel, DetailsModel}
41 import gr.grnet.aquarium.message.MessageConstants
42 import gr.grnet.aquarium.message.avro.gen.{AnyValueMsg, WalletEntryMsg, ResourcesChargingStateMsg, ResourceTypeMsg, ResourceInstanceChargingStateMsg, ResourceEventMsg}
43 import gr.grnet.aquarium.message.avro.{MessageHelpers, AvroHelpers, MessageFactory}
44 import gr.grnet.aquarium.util.LogHelpers.Debug
45 import gr.grnet.aquarium.{AquariumInternalError, Aquarium}
46
47 /**
48  * In practice a resource usage will be charged for the total amount of usage
49  * between resource usage changes.
50  *
51  * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
52  *
53  * @author Christos KK Loverdos <loverdos@gmail.com>
54  */
55 final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
56
57   def computeCreditsToSubtract(
58       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
59       oldCredits: Double,
60       timeDeltaMillis: Long,
61       unitPrice: Double
62   ): (Double /* credits */, String /* explanation */) = {
63
64     val oldAccumulatingAmount = resourceInstanceChargingState.getOldAccumulatingAmount
65     val credits = HrsOfMillis(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
66     val explanation = "Hours(%s) * MBs(%s) * UnitPrice(%s)".format(
67       HrsOfMillis(timeDeltaMillis),
68       MBsOfBytes(oldAccumulatingAmount),
69       unitPrice
70     )
71
72     (credits, explanation)
73   }
74
75   def computeSelectorPath(
76       chargingBehaviorDetails: DetailsModel.Type,
77       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
78       currentResourceEvent: ResourceEventMsg,
79       referenceStartMillis: Long,
80       referenceStopMillis: Long,
81       totalCredits: Double
82   ): List[String] = {
83     List(MessageConstants.DefaultSelectorKey)
84   }
85
86   def initialChargingDetails = {
87     DetailsModel.make
88   }
89
90   def computeNewAccumulatingAmount(
91       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
92       eventDetails: DetailsModel.Type
93   ) = {
94
95     val oldAccumulatingAmount = CreditsModel.from(resourceInstanceChargingState.getOldAccumulatingAmount)
96     val currentValue = CreditsModel.from(resourceInstanceChargingState.getCurrentValue)
97
98     CreditsModel.add(oldAccumulatingAmount, currentValue)
99   }
100
101   def constructImplicitEndEventFor(resourceEvent: ResourceEventMsg, newOccurredMillis: Long) = {
102     val details = resourceEvent.getDetails
103     val newDetails = DetailsModel.copyOf(details)
104     MessageHelpers.setAquariumSyntheticAndImplicitEnd(newDetails)
105
106     // FIXME: What value ?
107     ResourceEventMsg.newBuilder(resourceEvent).
108       setDetails(newDetails).
109       setOccurredMillis(newOccurredMillis).
110       setReceivedMillis(newOccurredMillis).
111       build()
112   }
113
114   def processResourceEvent(
115        aquarium: Aquarium,
116        resourceEvent: ResourceEventMsg,
117        resourceType: ResourceTypeMsg,
118        billingMonthInfo: BillingMonthInfo,
119        resourcesChargingState: ResourcesChargingStateMsg,
120        userStateModel: UserStateModel,
121        walletEntryRecorder: WalletEntryMsg ⇒ Unit
122    ): (Int, CreditsModel.Type) = {
123
124     // 1. Ensure proper initial state per resource and per instance
125     ensureInitializedWorkingState(resourcesChargingState, resourceEvent)
126
127     // 2. Fill in data from the new event
128     val stateOfResourceInstance = resourcesChargingState.getStateOfResourceInstance
129     val resourcesChargingStateDetails = resourcesChargingState.getDetails
130     val instanceID = resourceEvent.getInstanceID
131     val resourceInstanceChargingState = stateOfResourceInstance.get(instanceID)
132     fillWorkingResourceInstanceChargingStateFromEvent(resourceInstanceChargingState, resourceEvent)
133
134     val userAgreementHistoryModel = userStateModel.userAgreementHistoryModel
135     val previousEvents = resourceInstanceChargingState.getPreviousEvents
136     val previousEvent = previousEvents.size() match {
137       case 0 ⇒
138         // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
139         // Let's see if we can create a dummy previous event.
140         Debug(logger, "First event of its kind %s", AvroHelpers.jsonStringOfSpecificRecord(resourceEvent))
141
142         val dummyFirstEventValue = "0.0" // TODO ? From configuration
143
144         val millis = userAgreementHistoryModel.agreementByTimeslot.headOption match {
145           case None =>
146             throw new AquariumInternalError("No agreement!!!") // FIXME Better explanation
147           case Some((_,aggr)) =>
148             val millisAgg = aggr.timeslot.from.getTime
149             val millisMon = billingMonthInfo.monthStartMillis
150             if(millisAgg>millisMon) millisAgg else millisMon
151         }
152
153         val dummyFirstEvent = constructDummyFirstEventFor(resourceEvent, millis, dummyFirstEventValue)
154
155         Debug(logger, "Dummy first event %s", AvroHelpers.jsonStringOfSpecificRecord(dummyFirstEvent))
156         dummyFirstEvent
157
158
159       case _ ⇒
160         val previousEvent = previousEvents.get(0) // head is most recent
161         Debug(logger, "I have previous event %s", AvroHelpers.jsonStringOfSpecificRecord(previousEvent))
162         previousEvent
163
164     }
165
166     val retval = computeWalletEntriesForNewEvent(
167       resourceEvent,
168       resourceType,
169       billingMonthInfo,
170       userStateModel.totalCredits,
171       previousEvent.getOccurredMillis,
172       resourceEvent.getOccurredMillis,
173       userAgreementHistoryModel.agreementByTimeslot,
174       resourcesChargingStateDetails,
175       resourceInstanceChargingState,
176       aquarium,
177       walletEntryRecorder
178     )
179
180     // We need just one previous event, so we update it
181     MessageHelpers.setOnePreviousEvent(resourceInstanceChargingState, resourceEvent)
182
183     retval
184   }
185
186   def createVirtualEventsForRealtimeComputation(
187       userID: String,
188       resourceTypeName: String,
189       resourceInstanceID: String,
190       eventOccurredMillis: Long,
191       resourceInstanceChargingState: ResourceInstanceChargingStateMsg
192   ): List[ResourceEventMsg] = {
193     // FIXME This is too adhoc...
194     val path = resourceInstanceChargingState.getPreviousEvents.size() match {
195       case 0 ⇒
196         "unknown" // FIXME This should not happen. Throw?
197
198       case _ ⇒
199         val previousEvent = resourceInstanceChargingState.getPreviousEvents.get(0)
200         previousEvent.getDetails.get(MessageConstants.DetailsKeys.path) match {
201           case null ⇒
202             "unknown" // FIXME This should not happen. Throw?
203
204           case path ⇒
205             MessageHelpers.stringOfAnyValueMsg(path)
206         }
207     }
208
209     // FIXME This is too adhoc...
210     val action = resourceInstanceChargingState.getPreviousEvents.size() match {
211       case 0 ⇒
212         "unknown" // FIXME This should not happen. Throw?
213
214       case _ ⇒
215         val previousEvent = resourceInstanceChargingState.getPreviousEvents.get(0)
216         previousEvent.getDetails.get(MessageConstants.DetailsKeys.action) match {
217           case null ⇒
218             "update"
219
220           case action ⇒
221             MessageHelpers.stringOfAnyValueMsg(action)
222         }
223     }
224
225     MessageFactory.newResourceEventMsg(
226       MessageHelpers.VirtualEventsIDGen.nextUID(),
227       eventOccurredMillis,
228       eventOccurredMillis,
229       userID,
230       "aquarium",
231       resourceTypeName,
232       resourceInstanceID,
233       "0.0",
234       MessageConstants.EventVersion_1_0,
235       MessageFactory.newDetails(
236         MessageFactory.newBooleanDetail(MessageConstants.DetailsKeys.aquarium_is_synthetic, true),
237         MessageFactory.newBooleanDetail(MessageConstants.DetailsKeys.aquarium_is_realtime_virtual, true),
238         MessageFactory.newStringDetail(MessageConstants.DetailsKeys.path, path),
239         MessageFactory.newStringDetail(MessageConstants.DetailsKeys.action, action)
240       )
241     ) :: Nil
242   }
243 }
244
245 object ContinuousChargingBehavior {
246   private[this] final val TheOne = new ContinuousChargingBehavior
247
248   def apply(): ContinuousChargingBehavior = TheOne
249 }