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