00b6d252213df0b8f85f46ead3866c577fd68408
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / state / UserState.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.computation.state
37
38 import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
39 import gr.grnet.aquarium.charging.wallet.WalletEntry
40 import gr.grnet.aquarium.util.json.JsonSupport
41 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
42 import gr.grnet.aquarium.computation.BillingMonthInfo
43 import gr.grnet.aquarium.computation.parts.RoleHistory
44 import gr.grnet.aquarium.computation.state.parts.{OwnedResourcesMap, ResourceInstanceAmount, OwnedResourcesSnapshot, AgreementHistory, ImplicitlyIssuedResourceEventsSnapshot, LatestResourceEventsSnapshot}
45 import gr.grnet.aquarium.policy.UserAgreementModel
46 import gr.grnet.aquarium.charging.reason.{InitialUserStateSetup, NoSpecificChargingReason, ChargingReason}
47
48 /**
49  * A comprehensive representation of the User's state.
50  *
51  * Note that it is made of autonomous parts that are actually parts snapshots.
52  *
53  * The different snapshots need not agree on the snapshot time, ie. some state
54  * part may be stale, while other may be fresh.
55  *
56  * The user state is meant to be partially updated according to relevant events landing on Aquarium.
57  *
58  * @define communicatedByIM
59  *         This is communicated to Aquarium from the `IM` system.
60  *
61  *
62  * @param userID
63  * The user ID. $communicatedByIM
64  * @param userCreationMillis
65  * When the user was created.
66  * $communicatedByIM
67  * Set to zero if unknown.
68  * @param stateChangeCounter
69  * @param isFullBillingMonthState
70  * @param theFullBillingMonth
71  * @param implicitlyIssuedSnapshot
72  * @param latestResourceEventsSnapshot
73  * @param billingPeriodOutOfSyncResourceEventsCounter
74  * @param agreementHistory
75  * @param ownedResourcesSnapshot
76  * @param newWalletEntries
77  * The wallet entries computed. Not all user states need to holds wallet entries,
78  * only those that refer to billing periods (end of billing period).
79  * @param lastChangeReason
80  * The [[gr.grnet.aquarium.charging.reason.ChargingReason]] for which the usr state has changed.
81  * @param parentUserStateIDInStore
82  * The `ID` of the parent state. The parent state is the one used as a reference point in order to calculate
83  * this user state.
84  * @param _id
85  * The unique `ID` given by the store.
86  *
87  * @author Christos KK Loverdos <loverdos@gmail.com>
88  */
89 case class UserState(
90     isInitial: Boolean,
91     userID: String,
92
93     userCreationMillis: Long,
94
95     /**
96      * Each time the user state is updated, this must be increased.
97      * The counter is used when accessing user state from the cache (user state store)
98      * in order to get the latest value for a particular billing period.
99      */
100     stateChangeCounter: Long,
101
102     /**
103      * True iff this user state refers to a full billing period, that is a full billing month.
104      */
105     isFullBillingMonthState: Boolean,
106
107     /**
108      * The full billing period for which this user state refers to.
109      * This is set when the user state refers to a full billing period (= month)
110      * and is used to cache the user state for subsequent queries.
111      */
112     theFullBillingMonth: Option[BillingMonthInfo],
113
114     /**
115      * If this is a state for a full billing month, then keep here the implicit OFF
116      * resource events or any other whose cost policy demands an implicit event at the end of the billing period.
117      *
118      * The use case is this: A VM may have been started (ON state) before the end of the billing period
119      * and ended (OFF state) after the beginning of the next billing period. In order to bill this, we must assume
120      * an implicit OFF even right at the end of the billing period and an implicit ON event with the beginning of the
121      * next billing period.
122      */
123     implicitlyIssuedSnapshot: ImplicitlyIssuedResourceEventsSnapshot,
124
125     /**
126      * The latest (previous) resource events per resource instance.
127      */
128     latestResourceEventsSnapshot: LatestResourceEventsSnapshot,
129
130     /**
131      * The out of sync events used to produce this user state for
132      * the billing period recorded by `billingPeriodSnapshot`
133      */
134     billingPeriodOutOfSyncResourceEventsCounter: Long,
135
136     totalCredits: Double,
137
138     roleHistory: RoleHistory,
139
140     agreementHistory: AgreementHistory,
141
142     ownedResourcesSnapshot: OwnedResourcesSnapshot,
143
144     newWalletEntries: List[WalletEntry],
145
146     occurredMillis: Long, // When this user state was computed
147
148     // The last known change reason for this userState
149     lastChangeReason: ChargingReason = NoSpecificChargingReason(),
150     // The user state we used to compute this one. Normally the (cached)
151     // state at the beginning of the billing period.
152     parentUserStateIDInStore: Option[String] = None,
153     _id: String = null
154 ) extends JsonSupport {
155
156   def idInStore: Option[String] = _id match {
157     case null ⇒ None
158     case _id ⇒ Some(_id.toString)
159   }
160
161   //  def userCreationDate = new Date(userCreationMillis)
162   //
163   //  def userCreationFormatedDate = new MutableDateCalc(userCreationMillis).toString
164
165   def findDSLAgreementForTime(at: Long): Option[UserAgreementModel] = {
166     agreementHistory.findForTime(at)
167   }
168
169   def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceAmount] = {
170     ownedResourcesSnapshot.findResourceInstanceSnapshot(resource, instanceId)
171   }
172
173   def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
174     ownedResourcesSnapshot.getResourceInstanceAmount(resource, instanceId, defaultValue)
175   }
176
177   def newWithResourcesSnapshotUpdate(resource: String, // resource name
178                                      instanceId: String, // resource instance id
179                                      newAmount: Double,
180                                      snapshotTime: Long): UserState = {
181
182     val (newResources, _, _) =
183       ownedResourcesSnapshot.computeResourcesSnapshotUpdate(resource, instanceId, newAmount, snapshotTime)
184
185     this.copy(
186       isInitial = false,
187       ownedResourcesSnapshot = newResources,
188       stateChangeCounter = this.stateChangeCounter + 1
189     )
190   }
191
192   def newWithChangeReason(changeReason: ChargingReason) = {
193     this.copy(
194       isInitial = false,
195       lastChangeReason = changeReason,
196       stateChangeCounter = this.stateChangeCounter + 1
197     )
198   }
199
200   def newWithRoleHistory(newRoleHistory: RoleHistory, changeReason: ChargingReason) = {
201     // FIXME: Also update agreement
202     this.copy(
203       isInitial = false,
204       stateChangeCounter = this.stateChangeCounter + 1,
205       roleHistory = newRoleHistory,
206       lastChangeReason = changeReason
207     )
208   }
209
210   def resourcesMap: OwnedResourcesMap = {
211     ownedResourcesSnapshot.toResourcesMap
212   }
213
214   def findLatestResourceEvent: Option[ResourceEventModel] = {
215     latestResourceEventsSnapshot.findTheLatest
216   }
217
218   def findLatestResourceEventID: Option[String] = {
219     latestResourceEventsSnapshot.findTheLatestID
220   }
221
222   def isLatestResourceEventIDEqualTo(toCheckID: String) = {
223     findLatestResourceEventID.map(_ == toCheckID).getOrElse(false)
224   }
225
226   //  def toShortString = "UserState(%s, %s, %s, %s, %s)".format(
227   //    userId,
228   //    _id,
229   //    parentUserStateId,
230   //    totalEventsProcessedCounter,
231   //    calculationReason)
232 }
233
234 object UserState {
235   def fromJson(json: String): UserState = {
236     StdConverters.AllConverters.convertEx[UserState](JsonTextFormat(json))
237   }
238
239   object JsonNames {
240     final val _id = "_id"
241     final val userID = "userID"
242     final val isFullBillingMonthState = "isFullBillingMonthState"
243     final val occurredMillis = "occurredMillis"
244     final val theFullBillingMonth_year  = "theFullBillingMonth.year"  // FQN
245     final val theFullBillingMonth_month = "theFullBillingMonth.month" // FQN
246
247     object theFullBillingMonth {
248       final val year = "year"
249       final val month = "month"
250     }
251
252   }
253
254   def createInitialUserState(
255       userID: String,
256       userCreationMillis: Long,
257       occurredMillis: Long,
258       totalCredits: Double,
259       initialAgreement: UserAgreementModel,
260       calculationReason: ChargingReason = InitialUserStateSetup(None)
261   ) = {
262
263     UserState(
264       true,
265       userID,
266       userCreationMillis,
267       0L,
268       false,
269       None,
270       ImplicitlyIssuedResourceEventsSnapshot.Empty,
271       LatestResourceEventsSnapshot.Empty,
272       0L,
273       totalCredits,
274       RoleHistory.initial(initialAgreement.role, userCreationMillis),
275       AgreementHistory.initial(initialAgreement),
276       OwnedResourcesSnapshot.Empty,
277       Nil,
278       occurredMillis,
279       calculationReason
280     )
281   }
282
283   def createInitialUserStateFromBootstrap(
284       usb: UserStateBootstrap,
285       occurredMillis: Long,
286       calculationReason: ChargingReason
287   ): UserState = {
288
289     createInitialUserState(
290       usb.userID,
291       usb.userCreationMillis,
292       occurredMillis,
293       usb.initialCredits,
294       usb.initialAgreement,
295       calculationReason
296     )
297   }
298 }