2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
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.
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.
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.
36 package gr.grnet.aquarium.computation
38 import org.bson.types.ObjectId
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}
50 * A comprehensive representation of the User's state.
52 * Note that it is made of autonomous parts that are actually data snapshots.
54 * The different snapshots need not agree on the snapshot time, ie. some state
55 * part may be stale, while other may be fresh.
57 * The user state is meant to be partially updated according to relevant events landing on Aquarium.
59 * @define communicatedByIM
60 * This is communicated to Aquarium from the `IM` system.
64 * The user ID. $communicatedByIM
65 * @param userCreationMillis
66 * When the user was created.
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
91 * The unique `ID` given by the store.
93 * @author Christos KK Loverdos <loverdos@gmail.com>
99 userCreationMillis: Long,
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.
106 stateChangeCounter: Long,
109 * True iff this user state refers to a full billing period, that is a full billing month.
111 isFullBillingMonthState: Boolean,
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.
118 theFullBillingMonth: BillingMonthInfo,
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.
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.
129 implicitlyIssuedSnapshot: ImplicitlyIssuedResourceEventsSnapshot,
132 * So far computed wallet entries for the current billing month.
134 billingMonthWalletEntries: List[WalletEntry],
137 * Wallet entries that were computed for out of sync events.
138 * (for the current billing month ??)
140 outOfSyncWalletEntries: List[WalletEntry],
143 * The latest (previous) resource events per resource instance.
145 latestResourceEventsSnapshot: LatestResourceEventsSnapshot,
148 * Counts the total number of resource events used to produce this user state for
149 * the billing period recorded by `billingPeriodSnapshot`
151 billingPeriodResourceEventsCounter: Long,
154 * The out of sync events used to produce this user state for
155 * the billing period recorded by `billingPeriodSnapshot`
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 {
173 def idOpt: Option[String] = _id match {
175 case _id ⇒ Some(_id.toString)
178 // def userCreationDate = new Date(userCreationMillis)
180 // def userCreationFormatedDate = new MutableDateCalc(userCreationMillis).toString
182 def findDSLAgreementForTime(at: Long): Option[DSLAgreement] = {
183 agreementsSnapshot.findForTime(at)
186 def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
187 ownedResourcesSnapshot.findResourceInstanceSnapshot(resource, instanceId)
190 def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
191 ownedResourcesSnapshot.getResourceInstanceAmount(resource, instanceId, defaultValue)
194 def copyForResourcesSnapshotUpdate(resource: String, // resource name
195 instanceId: String, // resource instance id
197 snapshotTime: Long): UserState = {
199 val (newResources, _, _) = ownedResourcesSnapshot.computeResourcesSnapshotUpdate(resource, instanceId, newAmount, snapshotTime)
202 ownedResourcesSnapshot = newResources,
203 stateChangeCounter = this.stateChangeCounter + 1)
206 def copyForChangeReason(changeReason: UserStateChangeReason) = {
207 this.copy(lastChangeReason = changeReason)
210 def resourcesMap = ownedResourcesSnapshot.toResourcesMap
212 def modifyFromIMEvent(imEvent: IMEventModel, snapshotMillis: Long): UserState = {
215 imStateSnapshot = IMStateSnapshot(imEvent),
216 lastChangeReason = IMEventArrival(imEvent),
217 occurredMillis = snapshotMillis
221 // def toShortString = "UserState(%s, %s, %s, %s, %s)".format(
224 // parentUserStateId,
225 // totalEventsProcessedCounter,
226 // calculationReason)
230 def fromJson(json: String): UserState = {
231 StdConverters.AllConverters.convertEx[UserState](JsonTextFormat(json))
235 final val _id = "_id"
236 final val userID = "userID"
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))
245 val userID = imEvent.userID
246 val userCreationMillis = imEvent.occurredMillis
255 ImplicitlyIssuedResourceEventsSnapshot(List()),
258 LatestResourceEventsSnapshot(List()),
261 IMStateSnapshot(imEvent),
262 CreditSnapshot(credits),
263 AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))),
264 OwnedResourcesSnapshot(Nil),
267 InitialUserStateSetup
271 def createInitialUserState(userID: String,
272 userCreationMillis: Long,
275 roleNames: List[String] = List(),
276 agreementName: String = DSLAgreement.DefaultAgreementName) = {
277 val now = userCreationMillis
286 ImplicitlyIssuedResourceEventsSnapshot(List()),
289 LatestResourceEventsSnapshot(List()),
297 isActive, roleNames.headOption.getOrElse("default"),
299 IMEventModel.EventTypeNames.create, Map())
301 CreditSnapshot(credits),
302 AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))),
303 OwnedResourcesSnapshot(Nil),
306 InitialUserStateSetup
310 def createInitialUserStateFrom(us: UserState): UserState = {
311 createInitialUserState(
312 us.imStateSnapshot.imEvent,
313 us.creditsSnapshot.creditAmount,
314 us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last)