7cf2f27f9c09afd5d52fbd52b7e04e0e8af5969d
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / VMChargingBehavior.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 VMChargingBehavior.SelectorLabels.PowerStatus
39 import VMChargingBehavior.Selectors.Power
40 import gr.grnet.aquarium.charging.state.UserAgreementHistoryModel
41 import gr.grnet.aquarium.computation.BillingMonthInfo
42 import gr.grnet.aquarium.event.DetailsModel
43 import gr.grnet.aquarium.message.MessageConstants
44 import gr.grnet.aquarium.message.avro.gen.{UserStateMsg, WalletEntryMsg, ResourceTypeMsg, ResourcesChargingStateMsg, ResourceInstanceChargingStateMsg, ResourceEventMsg}
45 import gr.grnet.aquarium.message.avro.{AvroHelpers, MessageHelpers, MessageFactory}
46 import gr.grnet.aquarium.util.LogHelpers._
47 import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
48 import scala.collection.JavaConverters.asScalaBufferConverter
49 import gr.grnet.aquarium.Real
50 import gr.grnet.aquarium.HrsOfMillis
51
52 /**
53  * The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage.
54  *
55  * @author Christos KK Loverdos <loverdos@gmail.com>
56  */
57 final class VMChargingBehavior extends ChargingBehaviorSkeleton(List(PowerStatus)) {
58   def computeCreditsToSubtract(
59       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
60       oldCredits: Real,
61       timeDeltaMillis: Long,
62       unitPrice: Real
63   ): (Real, String /* explanation */) = {
64
65     val credits = HrsOfMillis(timeDeltaMillis) * unitPrice
66     val explanation = "Hours(%s) * UnitPrice(%s)".format(HrsOfMillis(timeDeltaMillis), unitPrice)
67
68     (credits, explanation)
69
70   }
71
72   def computeSelectorPath(
73       chargingBehaviorDetails: DetailsModel.Type,
74       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
75       currentResourceEvent: ResourceEventMsg,
76       referenceFromMillis: Long,
77       referenceToMillis: Long,
78       totalCredits: Real
79   ): List[String] = {
80     val previousEvents = resourceInstanceChargingState.getPreviousEvents.asScala.toList
81     (currentResourceEvent.getValue.toInt,previousEvents) match {
82       case (1,Nil) => // create --> on
83         //List(Power.create)
84         Nil
85       case (x,hd::_) =>  (x,hd.getValue.toInt) match {
86         case (1,0) => // off ---> on
87           List(Power.powerOff)
88         case (0,1) => // on ---> off
89           List(Power.powerOn)
90         case (2,1) => // off --> destroy
91           //List(Power.powerOff,Power.destroy)
92           Nil
93         case _ =>
94           throw new AquariumInternalError("Invalid state") // FIXME better message
95       }
96       case _ =>
97         throw new AquariumInternalError("Invalid state") // FIXME better message
98     }
99   }
100
101   def initialChargingDetails = {
102     DetailsModel.make
103   }
104
105   def computeNewAccumulatingAmount(
106       resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
107       eventDetails: DetailsModel.Type
108   ) = {
109     Real(resourceInstanceChargingState.getCurrentValue)
110   }
111
112   def constructImplicitEndEventFor(resourceEvent: ResourceEventMsg, newOccurredMillis: Long) = {
113     assert(VMChargingBehaviorValues.isONValue(resourceEvent.getValue))
114
115     val details = resourceEvent.getDetails
116     val newDetails = DetailsModel.copyOf(details)
117     MessageHelpers.setAquariumSyntheticAndImplicitEnd(newDetails)
118
119     ResourceEventMsg.newBuilder(resourceEvent).
120       setDetails(newDetails).
121       setOccurredMillis(newOccurredMillis).
122       setReceivedMillis(newOccurredMillis).
123       setValue(VMChargingBehaviorValues.OFF).
124       build()
125   }
126
127   def constructImplicitStartEventFor(resourceEvent: ResourceEventMsg) = {
128     throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s", this)
129   }
130
131   /**
132    *
133    * @return The number of wallet entries recorded and the new total credits
134    */
135   override def processResourceEvent(
136       aquarium: Aquarium,
137       resourceEvent: ResourceEventMsg,
138       resourceType: ResourceTypeMsg,
139       billingMonthInfo: BillingMonthInfo,
140       resourcesChargingState: ResourcesChargingStateMsg,
141       userAgreementHistoryModel: UserAgreementHistoryModel,
142       userStateMsg: UserStateMsg,
143       walletEntryRecorder: WalletEntryMsg ⇒ Unit
144   ): (Int, Real) = {
145
146     // 1. Ensure proper initial state per resource and per instance
147     ensureInitializedWorkingState(resourcesChargingState,resourceEvent)
148
149     // 2. Fill in data from the new event
150     val stateOfResourceInstance = resourcesChargingState.getStateOfResourceInstance
151     val resourcesChargingStateDetails = resourcesChargingState.getDetails
152     val instanceID = resourceEvent.getInstanceID
153     val resourceInstanceChargingState = stateOfResourceInstance.get(instanceID)
154     fillWorkingResourceInstanceChargingStateFromEvent(resourceInstanceChargingState, resourceEvent)
155
156     val previousEvents = resourceInstanceChargingState.getPreviousEvents
157     val retVal = previousEvents.size() match {
158       case 0 ⇒
159         (0, Real.Zero)
160
161       case _ ⇒
162         val previousEvent = previousEvents.get(0) // head is most recent
163         Debug(logger, "I have previous event %s", AvroHelpers.jsonStringOfSpecificRecord(previousEvent))
164
165         computeWalletEntriesForNewEvent(
166           resourceEvent,
167           resourceType,
168           billingMonthInfo,
169           Real(userStateMsg.getTotalCredits),
170           previousEvent.getOccurredMillis,
171           resourceEvent.getOccurredMillis,
172           userAgreementHistoryModel.agreementByTimeslot,
173           resourcesChargingStateDetails,
174           resourceInstanceChargingState,
175           aquarium,
176           walletEntryRecorder
177         )
178     }
179
180     // We need just one previous event, so we update it
181     MessageHelpers.setOnePreviousEvent(resourceInstanceChargingState, resourceEvent)
182     retVal
183   }
184
185   def createVirtualEventsForRealtimeComputation(
186       userID: String,
187       resourceTypeName: String,
188       resourceInstanceID: String,
189       eventOccurredMillis: Long,
190       resourceInstanceChargingState: ResourceInstanceChargingStateMsg
191   ): List[ResourceEventMsg] = {
192     val resourceInstanceID = resourceInstanceChargingState.getInstanceID
193
194     def vmEvent(value: String) : List[ResourceEventMsg] = {
195       val dm = DetailsModel.make
196       DetailsModel.setBoolean(dm, MessageConstants.DetailsKeys.aquarium_is_synthetic)
197       DetailsModel.setBoolean(dm, MessageConstants.DetailsKeys.aquarium_is_realtime_virtual)
198
199       MessageFactory.newResourceEventMsg(
200         MessageHelpers.VirtualEventsIDGen.nextUID(),
201         eventOccurredMillis,
202         eventOccurredMillis,
203         userID,
204         "aquarium",
205         resourceTypeName,
206         resourceInstanceID,
207         value,
208         MessageConstants.EventVersion_1_0,
209         dm
210       ) :: Nil
211     }
212
213     def mkON  = vmEvent(VMChargingBehaviorValues.ON)
214     def mkOFF = vmEvent(VMChargingBehaviorValues.OFF)
215
216     val previousEvents = resourceInstanceChargingState.getPreviousEvents
217     previousEvents.size() match {
218       case 0 ⇒  Nil
219       case _ ⇒
220
221         previousEvents.get(0).getValue.toInt match {
222        case 0 ⇒  mkON //produce an on event
223        case 1 ⇒  mkOFF //produce an off event
224        case 2 ⇒  mkOFF //produce an off event
225       }
226     }
227   }
228 }
229
230 object VMChargingBehavior {
231   object SelectorLabels {
232     final val PowerStatus = "Power Status (ON/OFF)"
233   }
234
235   object Selectors {
236     object Power {
237       // When the VM is created
238       //final val create = "create"
239
240       // When the VM is destroyed
241       //final val destroy = "destroy"
242
243       // When the VM is powered on
244       final val powerOn = "powerOn"
245
246       // When the VM is powered off
247       final val powerOff = "powerOff"
248     }
249   }
250 }