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