From 07baa764569601277703c2b79cb11850d679fc00 Mon Sep 17 00:00:00 2001 From: Christos KK Loverdos Date: Mon, 7 May 2012 18:21:58 +0300 Subject: [PATCH] Refactor charging computation + data --- .../actor/message/GetUserStateResponse.scala | 2 +- .../aquarium/actor/service/user/UserActor.scala | 5 +- .../aquarium/computation/BillingMonthInfo.scala | 92 +++++ .../DefaultUserStateComputations.scala | 4 +- .../aquarium/{user => computation}/UserState.scala | 273 +++++-------- .../UserStateComputations.scala | 286 ++----------- .../aquarium/computation/UserStateWorker.scala | 188 +++++++++ .../computation/data/AgreementSnapshot.scala | 57 +++ .../computation/data/AgreementsSnapshot.scala | 80 ++++ .../aquarium/computation/data/CreditSnapshot.scala | 44 ++ .../computation/data/IMStateSnapshot.scala | 45 +++ .../data/IgnoredFirstResourceEventsSnapshot.scala | 54 +++ .../data/IgnoredFirstResourceEventsWorker.scala | 67 ++++ .../ImplicitlyIssuedResourceEventsSnapshot.scala | 61 +++ .../ImplicitlyIssuedResourceEventsWorker.scala | 72 ++++ .../data/LatestResourceEventsSnapshot.scala | 62 +++ .../data/LatestResourceEventsWorker.scala | 89 ++++ .../computation/data/OwnedResourcesMap.scala | 54 +++ .../computation/data/OwnedResourcesSnapshot.scala | 90 +++++ .../data/ResourceInstanceSnapshot.scala | 71 ++++ .../computation/reason/UserStateChangeReason.scala | 133 ++++++ .../reason/UserStateChangeReasonCodes.scala | 51 +++ .../aquarium/logic/accounting/Accounting.scala | 4 +- .../gr/grnet/aquarium/store/UserStateStore.scala | 5 +- .../gr/grnet/aquarium/store/memory/MemStore.scala | 10 +- .../aquarium/store/mongodb/MongoDBStore.scala | 8 +- .../gr/grnet/aquarium/user/UserDataSnapshot.scala | 423 -------------------- .../gr/grnet/aquarium/util/ContextualLogger.scala | 98 +++-- .../aquarium/user/UserStateComputationsTest.scala | 10 +- 29 files changed, 1505 insertions(+), 933 deletions(-) create mode 100644 src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala rename src/main/scala/gr/grnet/aquarium/{user => computation}/DefaultUserStateComputations.scala (93%) rename src/main/scala/gr/grnet/aquarium/{user => computation}/UserState.scala (60%) rename src/main/scala/gr/grnet/aquarium/{user => computation}/UserStateComputations.scala (64%) create mode 100644 src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala create mode 100644 src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala delete mode 100644 src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala diff --git a/src/main/scala/gr/grnet/aquarium/actor/message/GetUserStateResponse.scala b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserStateResponse.scala index 273b477..b7158fd 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/message/GetUserStateResponse.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/message/GetUserStateResponse.scala @@ -35,7 +35,7 @@ package gr.grnet.aquarium.actor.message -import gr.grnet.aquarium.user.UserState +import gr.grnet.aquarium.computation.UserState /** * diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala index 5b73185..96c4d61 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala @@ -38,7 +38,6 @@ package service package user import gr.grnet.aquarium.actor._ -import gr.grnet.aquarium.user._ import gr.grnet.aquarium.util.shortClassNameOf import message.config.{ActorProviderConfigured, AquariumPropertiesLoaded} @@ -48,6 +47,8 @@ import gr.grnet.aquarium.Configurator import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc} import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent} import gr.grnet.aquarium.actor.message.{GetUserStateResponse, GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest} +import gr.grnet.aquarium.computation.data.IMStateSnapshot +import gr.grnet.aquarium.computation.UserState /** * @@ -123,7 +124,7 @@ class UserActor extends ReflectiveRoleableActor { } } - this._imState = IMStateSnapshot(imEvent, now) + this._imState = IMStateSnapshot(imEvent) DEBUG("%s %s", if(hadIMState) "Update" else "Set", shortClassNameOf(this._imState)) } diff --git a/src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala b/src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala new file mode 100644 index 0000000..8e23a79 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation + +import gr.grnet.aquarium.util.date.MutableDateCalc + +/** + * + * @author Christos KK Loverdos + */ + +final class BillingMonthInfo private(val year: Int, + val month: Int, + val startMillis: Long, + val stopMillis: Long) extends Ordered[BillingMonthInfo] { + + def previousMonth: BillingMonthInfo = { + BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goPreviousMonth) + } + + def nextMonth: BillingMonthInfo = { + BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goNextMonth) + } + + + def compare(that: BillingMonthInfo) = { + val ds = this.startMillis - that.startMillis + if(ds < 0) -1 else if(ds == 0) 0 else 1 + } + + + override def equals(any: Any) = any match { + case that: BillingMonthInfo ⇒ + this.year == that.year && this.month == that.month // normally everything else MUST be the same by construction + case _ ⇒ + false + } + + override def hashCode() = { + 31 * year + month + } + + override def toString = "%s-%02d".format(year, month) +} + +object BillingMonthInfo { + def fromMillis(millis: Long): BillingMonthInfo = { + fromDateCalc(new MutableDateCalc(millis)) + } + + def fromDateCalc(mdc: MutableDateCalc): BillingMonthInfo = { + val year = mdc.getYear + val month = mdc.getMonthOfYear + val startMillis = mdc.goStartOfThisMonth.getMillis + val stopMillis = mdc.goEndOfThisMonth.getMillis // no need to `copy` here, since we are discarding `mdc` + + new BillingMonthInfo(year, month, startMillis, stopMillis) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/user/DefaultUserStateComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala similarity index 93% rename from src/main/scala/gr/grnet/aquarium/user/DefaultUserStateComputations.scala rename to src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala index 8365cc7..8d3284f 100644 --- a/src/main/scala/gr/grnet/aquarium/user/DefaultUserStateComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala @@ -33,10 +33,10 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.user +package gr.grnet.aquarium.computation /** - * Default implementation for [[gr.grnet.aquarium.user.UserStateComputations]]. + * Default implementation for [[gr.grnet.aquarium.computation.UserStateComputations]]. * * @author Christos KK Loverdos */ diff --git a/src/main/scala/gr/grnet/aquarium/user/UserState.scala b/src/main/scala/gr/grnet/aquarium/computation/UserState.scala similarity index 60% rename from src/main/scala/gr/grnet/aquarium/user/UserState.scala rename to src/main/scala/gr/grnet/aquarium/computation/UserState.scala index 9e0ad26..74ade1c 100644 --- a/src/main/scala/gr/grnet/aquarium/user/UserState.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserState.scala @@ -33,18 +33,18 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.user +package gr.grnet.aquarium.computation -import gr.grnet.aquarium.util.json.JsonSupport -import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement -import com.ckkloverdos.maybe.{Failed, Maybe} -import gr.grnet.aquarium.util.date.MutableDateCalc -import gr.grnet.aquarium.event.{NewWalletEntry, WalletEntry} import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters} -import gr.grnet.aquarium.AquariumException -import gr.grnet.aquarium.event.im.IMEventModel +import gr.grnet.aquarium.event.{NewWalletEntry, WalletEntry} import org.bson.types.ObjectId - +import gr.grnet.aquarium.util.json.JsonSupport +import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement +import com.ckkloverdos.maybe.Maybe +import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup, IMEventArrival} +import gr.grnet.aquarium.AquariumInternalError +import gr.grnet.aquarium.computation.data.{AgreementSnapshot, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementsSnapshot, CreditSnapshot, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot, IMStateSnapshot} +import gr.grnet.aquarium.event.im.{StdIMEvent, IMEventModel} /** * A comprehensive representation of the User's state. @@ -82,7 +82,7 @@ import org.bson.types.ObjectId * The wallet entries computed. Not all user states need to holds wallet entries, * only those that refer to billing periods (end of billing period). * @param lastChangeReason - * The [[gr.grnet.aquarium.user.UserStateChangeReason]] for which the usr state has changed. + * The [[gr.grnet.aquarium.computation.reason.UserStateChangeReason]] for which the usr state has changed. * @param totalEventsProcessedCounter * @param parentUserStateId * The `ID` of the parent state. The parent state is the one used as a reference point in order to calculate @@ -157,9 +157,10 @@ case class UserState( billingPeriodOutOfSyncResourceEventsCounter: Long, imStateSnapshot: IMStateSnapshot, creditsSnapshot: CreditSnapshot, - agreementsSnapshot: AgreementSnapshot, + agreementsSnapshot: AgreementsSnapshot, ownedResourcesSnapshot: OwnedResourcesSnapshot, newWalletEntries: List[NewWalletEntry], + occurredMillis: Long, // The time fro which this state is relevant // The last known change reason for this userState lastChangeReason: UserStateChangeReason = NoSpecificChangeReason, totalEventsProcessedCounter: Long = 0L, @@ -169,20 +170,6 @@ case class UserState( _id: ObjectId = new ObjectId() ) extends JsonSupport { - private[this] def _allSnapshots: List[Long] = { - List( - imStateSnapshot.snapshotTime, - creditsSnapshot.snapshotTime, agreementsSnapshot.snapshotTime, - ownedResourcesSnapshot.snapshotTime, - implicitlyIssuedSnapshot.snapshotTime, - latestResourceEventsSnapshot.snapshotTime - ) - } - - def oldestSnapshotTime: Long = _allSnapshots min - - def newestSnapshotTime: Long = _allSnapshots max - def idOpt: Option[String] = _id match { case null ⇒ None case _id ⇒ Some(_id.toString) @@ -192,16 +179,11 @@ case class UserState( // // def userCreationFormatedDate = new MutableDateCalc(userCreationMillis).toString - def maybeDSLAgreement(at: Long): Maybe[DSLAgreement] = { - agreementsSnapshot match { - case snapshot @ AgreementSnapshot(data, _) ⇒ - snapshot.getAgreement(at) - case _ ⇒ - Failed(new AquariumException("No agreement snapshot found for user %s".format(userID))) - } + def findDSLAgreementForTime(at: Long): Option[DSLAgreement] = { + agreementsSnapshot.findForTime(at) } - def findResourceInstanceSnapshot(resource: String, instanceId: String): Maybe[ResourceInstanceSnapshot] = { + def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = { ownedResourcesSnapshot.findResourceInstanceSnapshot(resource, instanceId) } @@ -220,7 +202,7 @@ case class UserState( ownedResourcesSnapshot = newResources, stateChangeCounter = this.stateChangeCounter + 1) } - + def copyForChangeReason(changeReason: UserStateChangeReason) = { this.copy(lastChangeReason = changeReason) } @@ -230,8 +212,9 @@ case class UserState( def modifyFromIMEvent(imEvent: IMEventModel, snapshotMillis: Long): UserState = { this.copy( isInitial = false, - imStateSnapshot = IMStateSnapshot(imEvent, snapshotMillis), - lastChangeReason = IMEventArrival(imEvent) + imStateSnapshot = IMStateSnapshot(imEvent), + lastChangeReason = IMEventArrival(imEvent), + occurredMillis = snapshotMillis ) } @@ -243,7 +226,6 @@ case class UserState( // calculationReason) } - object UserState { def fromJson(json: String): UserState = { StdConverters.AllConverters.convertEx[UserState](JsonTextFormat(json)) @@ -253,157 +235,82 @@ object UserState { final val _id = "_id" final val userID = "userID" } -} - -final class BillingMonthInfo private(val year: Int, - val month: Int, - val startMillis: Long, - val stopMillis: Long) extends Ordered[BillingMonthInfo] { - - def previousMonth: BillingMonthInfo = { - BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goPreviousMonth) - } - - def nextMonth: BillingMonthInfo = { - BillingMonthInfo.fromDateCalc(new MutableDateCalc(year, month).goNextMonth) - } + def createInitialUserState(imEvent: IMEventModel, credits: Double, agreementName: String) = { + if(!imEvent.isCreateUser) { + throw new AquariumInternalError( + "Got '%s' instead of '%s'".format(imEvent.eventType, IMEventModel.EventTypeNames.create)) + } - def compare(that: BillingMonthInfo) = { - val ds = this.startMillis - that.startMillis - if(ds < 0) -1 else if(ds == 0) 0 else 1 - } - - - override def equals(any: Any) = any match { - case that: BillingMonthInfo ⇒ - this.year == that.year && this.month == that.month // normally everything else MUST be the same by construction - case _ ⇒ - false - } - - override def hashCode() = { - 31 * year + month + val userID = imEvent.userID + val userCreationMillis = imEvent.occurredMillis + + UserState( + true, + userID, + userCreationMillis, + 0L, + false, + null, + ImplicitlyIssuedResourceEventsSnapshot(List()), + Nil, + Nil, + LatestResourceEventsSnapshot(List()), + 0L, + 0L, + IMStateSnapshot(imEvent), + CreditSnapshot(credits), + AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))), + OwnedResourcesSnapshot(Nil), + Nil, + userCreationMillis, + InitialUserStateSetup + ) } - override def toString = "%s-%02d".format(year, month) -} - -object BillingMonthInfo { - def fromMillis(millis: Long): BillingMonthInfo = { - fromDateCalc(new MutableDateCalc(millis)) + def createInitialUserState(userID: String, + userCreationMillis: Long, + isActive: Boolean, + credits: Double, + roleNames: List[String] = List(), + agreementName: String = DSLAgreement.DefaultAgreementName) = { + val now = userCreationMillis + + UserState( + true, + userID, + userCreationMillis, + 0L, + false, + null, + ImplicitlyIssuedResourceEventsSnapshot(List()), + Nil, + Nil, + LatestResourceEventsSnapshot(List()), + 0L, + 0L, + IMStateSnapshot( + StdIMEvent( + "", + now, now, userID, + "", + isActive, roleNames.headOption.getOrElse("default"), + "1.0", + IMEventModel.EventTypeNames.create, Map()) + ), + CreditSnapshot(credits), + AgreementsSnapshot(List(AgreementSnapshot(agreementName, userCreationMillis))), + OwnedResourcesSnapshot(Nil), + Nil, + now, + InitialUserStateSetup + ) } - def fromDateCalc(mdc: MutableDateCalc): BillingMonthInfo = { - val year = mdc.getYear - val month = mdc.getMonthOfYear - val startMillis = mdc.goStartOfThisMonth.getMillis - val stopMillis = mdc.goEndOfThisMonth.getMillis // no need to `copy` here, since we are discarding `mdc` - - new BillingMonthInfo(year, month, startMillis, stopMillis) + def createInitialUserStateFrom(us: UserState): UserState = { + createInitialUserState( + us.imStateSnapshot.imEvent, + us.creditsSnapshot.creditAmount, + us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last) } } - -sealed trait UserStateChangeReason { - /** - * Return `true` if the result of the calculation should be stored back to the - * [[gr.grnet.aquarium.store.UserStateStore]]. - * - */ - def shouldStoreUserState: Boolean - - def shouldStoreCalculatedWalletEntries: Boolean - - def forPreviousBillingMonth: UserStateChangeReason - - def calculateCreditsForImplicitlyTerminated: Boolean - - def code: UserStateChangeReasonCodes.ChangeReasonCode -} - -object UserStateChangeReasonCodes { - type ChangeReasonCode = Int - - final val InitialSetupCode = 1 - final val NoSpecificChangeCode = 2 - final val MonthlyBillingCode = 3 - final val RealtimeBillingCode = 4 - final val IMEventArrivalCode = 5 -} - -case object InitialUserStateSetup extends UserStateChangeReason { - def shouldStoreUserState = true - - def shouldStoreCalculatedWalletEntries = false - - def forPreviousBillingMonth = this - - def calculateCreditsForImplicitlyTerminated = false - - def code = UserStateChangeReasonCodes.InitialSetupCode -} -/** - * A calculation made for no specific reason. Can be for testing, for example. - * - */ -case object NoSpecificChangeReason extends UserStateChangeReason { - def shouldStoreUserState = false - - def shouldStoreCalculatedWalletEntries = false - - def forBillingMonthInfo(bmi: BillingMonthInfo) = this - - def forPreviousBillingMonth = this - - def calculateCreditsForImplicitlyTerminated = false - - def code = UserStateChangeReasonCodes.NoSpecificChangeCode -} - -/** - * An authoritative calculation for the billing period. - * - * This marks a state for caching. - * - * @param billingMonthInfo - */ -case class MonthlyBillingCalculation(billingMonthInfo: BillingMonthInfo) extends UserStateChangeReason { - def shouldStoreUserState = true - - def shouldStoreCalculatedWalletEntries = true - - def forPreviousBillingMonth = MonthlyBillingCalculation(billingMonthInfo.previousMonth) - - def calculateCreditsForImplicitlyTerminated = true - - def code = UserStateChangeReasonCodes.MonthlyBillingCode -} - -/** - * Used for the realtime billing calculation. - * - * @param forWhenMillis The time this calculation is for - */ -case class RealtimeBillingCalculation(forWhenMillis: Long) extends UserStateChangeReason { - def shouldStoreUserState = false - - def shouldStoreCalculatedWalletEntries = false - - def forPreviousBillingMonth = this - - def calculateCreditsForImplicitlyTerminated = false - - def code = UserStateChangeReasonCodes.RealtimeBillingCode -} - -case class IMEventArrival(imEvent: IMEventModel) extends UserStateChangeReason { - def shouldStoreUserState = true - - def shouldStoreCalculatedWalletEntries = false - - def forPreviousBillingMonth = this - - def calculateCreditsForImplicitlyTerminated = false - - def code = UserStateChangeReasonCodes.IMEventArrivalCode -} diff --git a/src/main/scala/gr/grnet/aquarium/user/UserStateComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala similarity index 64% rename from src/main/scala/gr/grnet/aquarium/user/UserStateComputations.scala rename to src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala index c015377..09b1773 100644 --- a/src/main/scala/gr/grnet/aquarium/user/UserStateComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala @@ -33,8 +33,7 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.user - +package gr.grnet.aquarium.computation import scala.collection.mutable import gr.grnet.aquarium.util.{ContextualLogger, Loggable} @@ -47,89 +46,14 @@ import gr.grnet.aquarium.event.NewWalletEntry import gr.grnet.aquarium.event.resource.ResourceEventModel import gr.grnet.aquarium.event.im.{IMEventModel, StdIMEvent} import gr.grnet.aquarium.{AquariumInternalError, AquariumException} +import gr.grnet.aquarium.computation.data._ +import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup} /** * * @author Christos KK Loverdos */ class UserStateComputations extends Loggable { - def createInitialUserState(imEvent: IMEventModel, credits: Double, agreementName: String) = { - if(!imEvent.isCreateUser) { - throw new AquariumInternalError( - "Got '%s' instead of '%s'".format(imEvent.eventType, IMEventModel.EventTypeNames.create)) - } - - val userID = imEvent.userID - val userCreationMillis = imEvent.occurredMillis - val now = TimeHelpers.nowMillis() - - UserState( - true, - userID, - userCreationMillis, - 0L, - false, - null, - ImplicitlyIssuedResourceEventsSnapshot(List(), now), - Nil, - Nil, - LatestResourceEventsSnapshot(List(), now), - 0L, - 0L, - IMStateSnapshot(imEvent, now), - CreditSnapshot(credits, now), - AgreementSnapshot(List(Agreement(agreementName, userCreationMillis)), now), - OwnedResourcesSnapshot(Nil, now), - Nil, - InitialUserStateSetup - ) - } - - def createInitialUserState(userID: String, - userCreationMillis: Long, - isActive: Boolean, - credits: Double, - roleNames: List[String] = List(), - agreementName: String = DSLAgreement.DefaultAgreementName) = { - val now = userCreationMillis - - UserState( - true, - userID, - userCreationMillis, - 0L, - false, - null, - ImplicitlyIssuedResourceEventsSnapshot(List(), now), - Nil, - Nil, - LatestResourceEventsSnapshot(List(), now), - 0L, - 0L, - IMStateSnapshot( - StdIMEvent( - "", - now, now, userID, - "", - isActive, roleNames.headOption.getOrElse("default"), - "1.0", - IMEventModel.EventTypeNames.create, Map()), - now - ), - CreditSnapshot(credits, now), - AgreementSnapshot(List(Agreement(agreementName, userCreationMillis)), now), - OwnedResourcesSnapshot(Nil, now), - Nil, - InitialUserStateSetup - ) - } - - def createInitialUserStateFrom(us: UserState): UserState = { - createInitialUserState( - us.imStateSnapshot.imEvent, - us.creditsSnapshot.creditAmount, - us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last) - } def findUserStateAtEndOfBillingMonth(userId: String, billingMonthInfo: BillingMonthInfo, @@ -166,14 +90,14 @@ class UserStateComputations extends Loggable { val userCreationMillis = currentUserState.userCreationMillis val userCreationDateCalc = new MutableDateCalc(userCreationMillis) val billingMonthStartMillis = billingMonthInfo.startMillis - val billingMonthStopMillis = billingMonthInfo.stopMillis + val billingMonthStopMillis = billingMonthInfo.stopMillis if(billingMonthStopMillis < userCreationMillis) { // If the user did not exist for this billing month, piece of cake clog.debug("User did not exist before %s", userCreationDateCalc) // NOTE: Reason here will be: InitialUserStateSetup$ - val initialUserState0 = createInitialUserStateFrom(currentUserState) + val initialUserState0 = UserState.createInitialUserStateFrom(currentUserState) val initialUserState1 = userStateStore.insertUserState(initialUserState0) clog.debug("Returning INITIAL state [_id=%s] %s".format(initialUserState1._id, initialUserState1)) @@ -198,33 +122,33 @@ class UserStateComputations extends Loggable { case Some(latestUserState) ⇒ // Found a "latest" user state but need to see if it is indeed the true and one latest. // For this reason, we must count the events again. - val latestStateOOSEventsCounter = latestUserState.billingPeriodOutOfSyncResourceEventsCounter - val actualOOSEventsCounter = resourceEventStore.countOutOfSyncEventsForBillingPeriod( - userId, - billingMonthStartMillis, - billingMonthStopMillis) - - val counterDiff = actualOOSEventsCounter - latestStateOOSEventsCounter - counterDiff match { - // ZERO, we are OK! - case 0 ⇒ - // NOTE: Keep the caller's calculation reason - latestUserState.copyForChangeReason(calculationReason) - - // We had more, so must recompute - case n if n > 0 ⇒ - clog.debug( - "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n) - val result = doCompute - clog.end() - result - - // We had less???? - case n if n < 0 ⇒ - val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n) - clog.warn(errMsg) - throw new AquariumException(errMsg) - } + val latestStateOOSEventsCounter = latestUserState.billingPeriodOutOfSyncResourceEventsCounter + val actualOOSEventsCounter = resourceEventStore.countOutOfSyncEventsForBillingPeriod( + userId, + billingMonthStartMillis, + billingMonthStopMillis) + + val counterDiff = actualOOSEventsCounter - latestStateOOSEventsCounter + counterDiff match { + // ZERO, we are OK! + case 0 ⇒ + // NOTE: Keep the caller's calculation reason + latestUserState.copyForChangeReason(calculationReason) + + // We had more, so must recompute + case n if n > 0 ⇒ + clog.debug( + "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n) + val result = doCompute + clog.end() + result + + // We had less???? + case n if n < 0 ⇒ + val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n) + clog.warn(errMsg) + throw new AquariumException(errMsg) + } } } } @@ -233,6 +157,7 @@ class UserStateComputations extends Loggable { def rcDebugInfo(rcEvent: ResourceEventModel) = { rcEvent.toDebugString(false) } + //- Utility methods def processResourceEvent(startingUserState: UserState, @@ -329,7 +254,7 @@ class UserStateComputations extends Loggable { if(stateChangeReason.shouldStoreCalculatedWalletEntries) { val newWalletEntry = NewWalletEntry( - userStateWorker.userId, + userStateWorker.userID, newCreditsDiff, oldCredits, newCredits, @@ -353,7 +278,7 @@ class UserStateComputations extends Loggable { } _workingUserState = _workingUserState.copy( - creditsSnapshot = CreditSnapshot(newCredits, TimeHelpers.nowMillis()), + creditsSnapshot = CreditSnapshot(newCredits), stateChangeCounter = _workingUserState.stateChangeCounter + 1, totalEventsProcessedCounter = _workingUserState.totalEventsProcessedCounter + 1 ) @@ -492,7 +417,7 @@ class UserStateComputations extends Loggable { // Now, the previous and implicitly started must be our base for the following computation, so we create an // appropriate worker val specialUserStateWorker = UserStateWorker( - userStateWorker.userId, + userStateWorker.userID, LatestResourceEventsWorker.fromList(specialEvents), ImplicitlyIssuedResourceEventsWorker.Empty, IgnoredFirstResourceEventsWorker.Empty, @@ -535,142 +460,3 @@ class UserStateComputations extends Loggable { _workingUserState } } - -/** - * A helper object holding intermediate state/results during resource event processing. - * - * @param previousResourceEvents - * This is a collection of all the latest resource events. - * We want these in order to correlate incoming resource events with their previous (in `occurredMillis` time) - * ones. Will be updated on processing the next resource event. - * - * @param implicitlyIssuedStartEvents - * The implicitly issued resource events at the beginning of the billing period. - * - * @param ignoredFirstResourceEvents - * The resource events that were first (and unused) of their kind. - * - * @author Christos KK Loverdos - */ -case class UserStateWorker(userId: String, - previousResourceEvents: LatestResourceEventsWorker, - implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker, - ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker, - accounting: Accounting, - resourcesMap: DSLResourcesMap) { - - /** - * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource - * events and b) the explicit previous resource events. If the event is found, it is removed from the - * respective source. - * - * If the event is not found, then this must be for a new resource instance. - * (and probably then some `zero` resource event must be implied as the previous one) - * - * @param resource - * @param instanceId - * @return - */ - def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { - // implicitly issued events are checked first - implicitlyIssuedStartEvents.findAndRemoveResourceEvent(resource, instanceId) match { - case some @ Some(_) ⇒ - some - case None ⇒ - // explicit previous resource events are checked second - previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match { - case some @ Some(_) ⇒ - some - case _ ⇒ - None - } - } - } - - def updateIgnored(resourceEvent: ResourceEventModel): Unit = { - ignoredFirstResourceEvents.updateResourceEvent(resourceEvent) - } - - def updatePrevious(resourceEvent: ResourceEventModel): Unit = { - previousResourceEvents.updateResourceEvent(resourceEvent) - } - - def debugTheMaps(clog: ContextualLogger)(rcDebugInfo: ResourceEventModel ⇒ String): Unit = { - if(previousResourceEvents.size > 0) { - val map = previousResourceEvents.latestEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) } - clog.debugMap("previousResourceEvents", map, 0) - } - if(implicitlyIssuedStartEvents.size > 0) { - val map = implicitlyIssuedStartEvents.implicitlyIssuedEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) } - clog.debugMap("implicitlyTerminatedResourceEvents", map, 0) - } - if(ignoredFirstResourceEvents.size > 0) { - val map = ignoredFirstResourceEvents.ignoredFirstEventsMap.map { case (k, v) => (k, rcDebugInfo(v)) } - clog.debugMap("ignoredFirstResourceEvents", map, 0) - } - } - -// private[this] -// def allPreviousAndAllImplicitlyStarted: List[ResourceEvent] = { -// val buffer: FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]() -// -// buffer ++= implicitlyIssuedStartEvents.implicitlyIssuedEventsMap -// buffer ++= previousResourceEvents.latestEventsMap -// -// buffer.valuesIterator.toList -// } - - /** - * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit - * end events along with those implicitly issued events. Before returning, remove the events that generated the - * implicit ends from the internal state of this instance. - * - * @see [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]] - */ - def findAndRemoveGeneratorsOfImplicitEndEvents(newOccuredMillis: Long - ): (List[ResourceEventModel], List[ResourceEventModel]) = { - val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]() - val checkSet = mutable.Set[ResourceEventModel]() - - def doItFor(map: ResourceEventModel.FullMutableResourceTypeMap): Unit = { - val resourceEvents = map.valuesIterator - for { - resourceEvent ← resourceEvents - dslResource ← resourcesMap.findResource(resourceEvent.safeResource) - costPolicy = dslResource.costPolicy - } { - if(costPolicy.supportsImplicitEvents) { - if(costPolicy.mustConstructImplicitEndEventFor(resourceEvent)) { - val implicitEnd = costPolicy.constructImplicitEndEventFor(resourceEvent, newOccuredMillis) - - if(!checkSet.contains(resourceEvent)) { - checkSet.add(resourceEvent) - buffer append ((resourceEvent, implicitEnd)) - } - - // remove it anyway - map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceId)) - } - } - } - } - - doItFor(previousResourceEvents.latestEventsMap) // we give priority for previous - doItFor(implicitlyIssuedStartEvents.implicitlyIssuedEventsMap) // ... over implicitly issued... - - (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList) - } -} - -object UserStateWorker { - def fromUserState(userState: UserState, accounting: Accounting, resourcesMap: DSLResourcesMap): UserStateWorker = { - UserStateWorker( - userState.userID, - userState.latestResourceEventsSnapshot.toMutableWorker, - userState.implicitlyIssuedSnapshot.toMutableWorker, - IgnoredFirstResourceEventsWorker.Empty, - accounting, - resourcesMap - ) - } -} diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala new file mode 100644 index 0000000..ab5abb3 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala @@ -0,0 +1,188 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation + +import scala.collection.mutable +import gr.grnet.aquarium.logic.accounting.dsl.DSLResourcesMap +import gr.grnet.aquarium.logic.accounting.Accounting +import gr.grnet.aquarium.event.resource.ResourceEventModel +import gr.grnet.aquarium.computation.data.{LatestResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, IgnoredFirstResourceEventsWorker} +import gr.grnet.aquarium.util.ContextualLogger + +/** + * A helper object holding intermediate state/results during resource event processing. + * + * @param previousResourceEvents + * This is a collection of all the latest resource events. + * We want these in order to correlate incoming resource events with their previous (in `occurredMillis` time) + * ones. Will be updated on processing the next resource event. + * + * @param implicitlyIssuedStartEvents + * The implicitly issued resource events at the beginning of the billing period. + * + * @param ignoredFirstResourceEvents + * The resource events that were first (and unused) of their kind. + * + * @author Christos KK Loverdos + */ +case class UserStateWorker(userID: String, + previousResourceEvents: LatestResourceEventsWorker, + implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker, + ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker, + accounting: Accounting, + resourcesMap: DSLResourcesMap) { + + /** + * Finds the previous resource event by checking two possible sources: a) The implicitly terminated resource + * events and b) the explicit previous resource events. If the event is found, it is removed from the + * respective source. + * + * If the event is not found, then this must be for a new resource instance. + * (and probably then some `zero` resource event must be implied as the previous one) + * + * @param resource + * @param instanceId + * @return + */ + def findAndRemovePreviousResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { + // implicitly issued events are checked first + implicitlyIssuedStartEvents.findAndRemoveResourceEvent(resource, instanceId) match { + case some@Some(_) ⇒ + some + case None ⇒ + // explicit previous resource events are checked second + previousResourceEvents.findAndRemoveResourceEvent(resource, instanceId) match { + case some@Some(_) ⇒ + some + case _ ⇒ + None + } + } + } + + def updateIgnored(resourceEvent: ResourceEventModel): Unit = { + ignoredFirstResourceEvents.updateResourceEvent(resourceEvent) + } + + def updatePrevious(resourceEvent: ResourceEventModel): Unit = { + previousResourceEvents.updateResourceEvent(resourceEvent) + } + + def debugTheMaps(clog: ContextualLogger)(rcDebugInfo: ResourceEventModel ⇒ String): Unit = { + if(previousResourceEvents.size > 0) { + val map = previousResourceEvents.latestEventsMap.map { + case (k, v) => (k, rcDebugInfo(v)) + } + clog.debugMap("previousResourceEvents", map, 0) + } + if(implicitlyIssuedStartEvents.size > 0) { + val map = implicitlyIssuedStartEvents.implicitlyIssuedEventsMap.map { + case (k, v) => (k, rcDebugInfo(v)) + } + clog.debugMap("implicitlyTerminatedResourceEvents", map, 0) + } + if(ignoredFirstResourceEvents.size > 0) { + val map = ignoredFirstResourceEvents.ignoredFirstEventsMap.map { + case (k, v) => (k, rcDebugInfo(v)) + } + clog.debugMap("ignoredFirstResourceEvents", map, 0) + } + } + + // private[this] + // def allPreviousAndAllImplicitlyStarted: List[ResourceEvent] = { + // val buffer: FullMutableResourceTypeMap = scala.collection.mutable.Map[FullResourceType, ResourceEvent]() + // + // buffer ++= implicitlyIssuedStartEvents.implicitlyIssuedEventsMap + // buffer ++= previousResourceEvents.latestEventsMap + // + // buffer.valuesIterator.toList + // } + + /** + * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit + * end events along with those implicitly issued events. Before returning, remove the events that generated the + * implicit ends from the internal state of this instance. + * + * @see [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]] + */ + def findAndRemoveGeneratorsOfImplicitEndEvents(newOccuredMillis: Long + ): (List[ResourceEventModel], List[ResourceEventModel]) = { + val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]() + val checkSet = mutable.Set[ResourceEventModel]() + + def doItFor(map: ResourceEventModel.FullMutableResourceTypeMap): Unit = { + val resourceEvents = map.valuesIterator + for { + resourceEvent ← resourceEvents + dslResource ← resourcesMap.findResource(resourceEvent.safeResource) + costPolicy = dslResource.costPolicy + } { + if(costPolicy.supportsImplicitEvents) { + if(costPolicy.mustConstructImplicitEndEventFor(resourceEvent)) { + val implicitEnd = costPolicy.constructImplicitEndEventFor(resourceEvent, newOccuredMillis) + + if(!checkSet.contains(resourceEvent)) { + checkSet.add(resourceEvent) + buffer append ((resourceEvent, implicitEnd)) + } + + // remove it anyway + map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceId)) + } + } + } + } + + doItFor(previousResourceEvents.latestEventsMap) // we give priority for previous + doItFor(implicitlyIssuedStartEvents.implicitlyIssuedEventsMap) // ... over implicitly issued... + + (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList) + } +} + +object UserStateWorker { + def fromUserState(userState: UserState, accounting: Accounting, resourcesMap: DSLResourcesMap): UserStateWorker = { + UserStateWorker( + userState.userID, + userState.latestResourceEventsSnapshot.toMutableWorker, + userState.implicitlyIssuedSnapshot.toMutableWorker, + IgnoredFirstResourceEventsWorker.Empty, + accounting, + resourcesMap + ) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala new file mode 100644 index 0000000..1fc29c9 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementSnapshot.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.util.date.MutableDateCalc +import java.util.Date +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot + +/** + * Represents an agreement valid for a specific amount of time. By convention, + * if an agreement is currently valid, then the validTo field is equal to `Long.MaxValue`. + * + * @author Christos KK Loverdos + */ +case class AgreementSnapshot(name: String, validFrom: Long, validTo: Long = Long.MaxValue) { + require(validTo > validFrom) + require(!name.isEmpty) + + def timeslot = Timeslot(new Date(validFrom), new Date(validTo)) + + override def toString = + "AgreementSnapshot(%s, %s, %s)". + format(name, new MutableDateCalc(validFrom), new MutableDateCalc(validTo)) +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala new file mode 100644 index 0000000..a7c7860 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementsSnapshot.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import java.util.Date + +import gr.grnet.aquarium.logic.accounting.dsl.{DSLAgreement, Timeslot} +import gr.grnet.aquarium.logic.accounting.Policy +import scala.collection.immutable.{SortedMap, TreeMap} + +/** + * User agreement data that will be part of UserState. + * The provided list of agreements cannot have time gaps. This is checked at object creation type. + * + * Note: This is copied from UserDataSnapshot.scala/AgreementSnapshot. + * TODO: Review + * + * @author Christos KK Loverdos + */ + +case class AgreementsSnapshot(agreements: List[AgreementSnapshot]) { + ensureNoGaps(agreements.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false)) + + def ensureNoGaps(agreements: List[AgreementSnapshot]): Unit = agreements match { + case ha :: (t @ (hb :: tail)) => + assert(ha.validTo - hb.validFrom == 1); + ensureNoGaps(t) + case h :: Nil => + assert(h.validTo == Long.MaxValue) + case Nil => () + } + + def agreementsByTimeslot: SortedMap[Timeslot, String] = { + TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*) + } + + /** + * Get the user agreement at the specified timestamp + */ + def findForTime(at: Long): Option[DSLAgreement] = { + // FIXME: Refactor and do not make this static call to Policy + agreements.find{ x => x.validFrom < at && x.validTo > at} match { + case Some(x) => Policy.policy(new Date(at)).findAgreement(x.name) + case None => None + } + } +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala new file mode 100644 index 0000000..7ec81d9 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/CreditSnapshot.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + + +/** + * + * @author Christos KK Loverdos + */ + +case class CreditSnapshot(creditAmount: Double) diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala new file mode 100644 index 0000000..37efd53 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/IMStateSnapshot.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.event.im.IMEventModel + +/** + * + * @author Christos KK Loverdos + */ + +case class IMStateSnapshot(imEvent: IMEventModel) diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala new file mode 100644 index 0000000..f82c855 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsSnapshot.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.event.resource.ResourceEventModel + +/** + * + * @author Christos KK Loverdos + */ + +case class IgnoredFirstResourceEventsSnapshot(ignoredFirstEvents: List[ResourceEventModel]) { + def toMutableWorker = { + val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() + for(ignoredFirstEvent <- ignoredFirstEvents) { + map(ignoredFirstEvent.fullResourceInfo) = ignoredFirstEvent + } + + IgnoredFirstResourceEventsWorker(map) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala new file mode 100644 index 0000000..7936784 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/IgnoredFirstResourceEventsWorker.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.util.findAndRemoveFromMap +import gr.grnet.aquarium.event.resource.ResourceEventModel +import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap + +/** + * + * @author Christos KK Loverdos + */ +case class IgnoredFirstResourceEventsWorker(ignoredFirstEventsMap: FullMutableResourceTypeMap) { + def toImmutableSnapshot(snapshotTime: Long) = + IgnoredFirstResourceEventsSnapshot(ignoredFirstEventsMap.valuesIterator.toList) + + def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { + findAndRemoveFromMap(ignoredFirstEventsMap, (resource, instanceId)) + } + + def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = { + ignoredFirstEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent + } + + def size = ignoredFirstEventsMap.size + + def foreach[U](f: ResourceEventModel => U): Unit = { + ignoredFirstEventsMap.valuesIterator.foreach(f) + } +} + +object IgnoredFirstResourceEventsWorker { + final val Empty = IgnoredFirstResourceEventsWorker(scala.collection.mutable.Map()) +} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala new file mode 100644 index 0000000..52b94fc --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala @@ -0,0 +1,61 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.event.resource.ResourceEventModel + +/** + * Keeps the implicit OFF events when a billing period ends. + * This is normally recorded in the [[gr.grnet.aquarium.user.UserState]]. + * + * @author Christos KK Loverdos + */ +case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[ResourceEventModel]) { + /** + * The gateway to playing with mutable state. + * + * @return A fresh instance of [[gr.grnet.aquarium.computation.data.ImplicitlyIssuedResourceEventsWorker]]. + */ + def toMutableWorker = { + val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() + for(implicitEvent <- implicitlyIssuedEvents) { + map(implicitEvent.fullResourceInfo) = implicitEvent + } + + ImplicitlyIssuedResourceEventsWorker(map) + } +} + diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala new file mode 100644 index 0000000..0aec78d --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsWorker.scala @@ -0,0 +1,72 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.util.findAndRemoveFromMap +import gr.grnet.aquarium.event.resource.ResourceEventModel +import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap + + +/** + * This is the mutable cousin of [[gr.grnet.aquarium.computation.data.ImplicitlyIssuedResourceEventsSnapshot]]. + * + * @param implicitlyIssuedEventsMap + * + * @author Christos KK Loverdos + */ +case class ImplicitlyIssuedResourceEventsWorker(implicitlyIssuedEventsMap: FullMutableResourceTypeMap) { + + def toList: scala.List[ResourceEventModel] = { + implicitlyIssuedEventsMap.valuesIterator.toList + } + + def toImmutableSnapshot(snapshotTime: Long) = + ImplicitlyIssuedResourceEventsSnapshot(toList) + + def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { + findAndRemoveFromMap(implicitlyIssuedEventsMap, (resource, instanceId)) + } + + def size = implicitlyIssuedEventsMap.size + + def foreach[U](f: ResourceEventModel => U): Unit = { + implicitlyIssuedEventsMap.valuesIterator.foreach(f) + } +} + +object ImplicitlyIssuedResourceEventsWorker { + final val Empty = ImplicitlyIssuedResourceEventsWorker(scala.collection.mutable.Map()) +} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala new file mode 100644 index 0000000..6c86f7f --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.event.resource.ResourceEventModel + +/** + * Keeps the latest resource event per resource instance. + * + * + * @author Christos KK Loverdos + */ + +case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel]) { + + /** + * The gateway to playing with mutable state. + * + * @return A fresh instance of [[gr.grnet.aquarium.computation.data.LatestResourceEventsWorker]]. + */ + def toMutableWorker = { + val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() + for(latestEvent <- resourceEvents) { + map(latestEvent.fullResourceInfo) = latestEvent + } + LatestResourceEventsWorker(map) + } +} + diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala new file mode 100644 index 0000000..9690050 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsWorker.scala @@ -0,0 +1,89 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +import gr.grnet.aquarium.util.findAndRemoveFromMap +import gr.grnet.aquarium.event.resource.ResourceEventModel +import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap + +/** + * This is the mutable cousin of [[gr.grnet.aquarium.computation.data.LatestResourceEventsSnapshot]]. + * + * @param latestEventsMap + * + * @author Christos KK Loverdos + */ +case class LatestResourceEventsWorker(latestEventsMap: FullMutableResourceTypeMap) { + + /** + * The gateway to immutable state. + * + * @param snapshotTime The relevant snapshot time. + * @return A fresh instance of [[gr.grnet.aquarium.computation.data.LatestResourceEventsSnapshot]]. + */ + def toImmutableSnapshot(snapshotTime: Long) = + LatestResourceEventsSnapshot(latestEventsMap.valuesIterator.toList) + + def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = { + latestEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent + } + + def findResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { + latestEventsMap.get((resource, instanceId)) + } + + def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { + findAndRemoveFromMap(latestEventsMap, (resource, instanceId)) + } + + def size = latestEventsMap.size + + def foreach[U](f: ResourceEventModel => U): Unit = { + latestEventsMap.valuesIterator.foreach(f) + } +} + +object LatestResourceEventsWorker { + final val Empty = LatestResourceEventsWorker(scala.collection.mutable.Map()) + + /** + * Helper factory to construct a worker from a list of events. + */ + def fromList(latestEventsList: List[ResourceEventModel]): LatestResourceEventsWorker = { + LatestResourceEventsSnapshot(latestEventsList).toMutableWorker + } +} + diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala new file mode 100644 index 0000000..c144456 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesMap.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +/** + * A map from (resourceName, resourceInstanceId) to value. + * + * This representation is convenient for computations and updating, while the + * [[gr.grnet.aquarium.computation.data.OwnedResourcesSnapshot]] representation is convenient for JSON serialization. + * + * @author Christos KK Loverdos + */ + +class OwnedResourcesMap(resourcesMap: Map[(String, String), Double]) { + def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot = + OwnedResourcesSnapshot( + resourcesMap map { + case ((name, instanceId), value) ⇒ + ResourceInstanceSnapshot(name, instanceId, value) } toList + ) +} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala new file mode 100644 index 0000000..fd7649c --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala @@ -0,0 +1,90 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + + +/** + * + * @author Christos KK Loverdos + */ + +case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstanceSnapshot]) { + + def toResourcesMap: OwnedResourcesMap = { + val tuples = for(rc <- resourceInstanceSnapshots) yield ((rc.resource, rc.instanceId), (rc.instanceAmount)) + + new OwnedResourcesMap(Map(tuples.toSeq: _*)) + } + + def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = { + // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible + // (at least out of the box). Thus, the update is O(L), where L is the length of the data List. + resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId)) + } + + def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = { + resourceInstanceSnapshots.find(x => resource == x.resource && instanceId == x.instanceId) + } + + def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = { + findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue) + } + + def computeResourcesSnapshotUpdate(resource: String, // resource name + instanceId: String, // resource instance id + newAmount: Double, + snapshotTime: Long): (OwnedResourcesSnapshot, + Option[ResourceInstanceSnapshot], + ResourceInstanceSnapshot) = { + + val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount) + val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId) + + val newResourceInstances = oldResourceInstanceOpt match { + case Some(oldResourceInstance) ⇒ + // Resource instance found, so delete the old one and add the new one + newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId) + + case None ⇒ + // Resource not found, so this is the first time and we just add the new snapshot + newResourceInstance :: resourceInstanceSnapshots + } + + val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances) + + (newOwnedResources, oldResourceInstanceOpt, newResourceInstance) + } +} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala new file mode 100644 index 0000000..70cca44 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/data/ResourceInstanceSnapshot.scala @@ -0,0 +1,71 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.data + +/** + * Maintains the current state of a resource instance owned by the user. + * The encoding is as follows: + * + * name: DSLResource.name + * instanceId: instance-id (in the resource's descriminatorField) + * data: current-resource-value + * snapshotTime: last-update-timestamp + * + * In order to have a uniform representation of the resource state for all + * resource types (complex or simple) the following convention applies: + * + * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id) + * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1") + * + * @param resource Same as `resource` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]] + * @param instanceId Same as `instanceId` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]] + * @param instanceAmount This is the amount kept for the resource instance. +* The general rule is that an amount saved in a [[gr.grnet.aquarium.computation.data. ResourceInstanceSnapshot]] + * represents a total value, while a value appearing in a [[gr.grnet.aquarium.event.resource.ResourceEventModel]] + * represents a difference. How these two values are combined to form the new amount is dictated + * by the underlying [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]] + * + * @author Christos KK Loverdos + */ +case class ResourceInstanceSnapshot(resource: String, + instanceId: String, + instanceAmount: Double) { + + def isSameResourceInstance(resource: String, instanceId: String) = { + this.resource == resource && + this.instanceId == instanceId + } +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala new file mode 100644 index 0000000..556df83 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala @@ -0,0 +1,133 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.reason + +import gr.grnet.aquarium.computation.BillingMonthInfo +import gr.grnet.aquarium.event.im.IMEventModel + +sealed trait UserStateChangeReason { + /** + * Return `true` if the result of the calculation should be stored back to the + * [[gr.grnet.aquarium.store.UserStateStore]]. + * + */ + def shouldStoreUserState: Boolean + + def shouldStoreCalculatedWalletEntries: Boolean + + def forPreviousBillingMonth: UserStateChangeReason + + def calculateCreditsForImplicitlyTerminated: Boolean + + def code: UserStateChangeReasonCodes.ChangeReasonCode +} + +case object InitialUserStateSetup extends UserStateChangeReason { + def shouldStoreUserState = true + + def shouldStoreCalculatedWalletEntries = false + + def forPreviousBillingMonth = this + + def calculateCreditsForImplicitlyTerminated = false + + def code = UserStateChangeReasonCodes.InitialSetupCode +} +/** + * A calculation made for no specific reason. Can be for testing, for example. + * + */ +case object NoSpecificChangeReason extends UserStateChangeReason { + def shouldStoreUserState = false + + def shouldStoreCalculatedWalletEntries = false + + def forBillingMonthInfo(bmi: BillingMonthInfo) = this + + def forPreviousBillingMonth = this + + def calculateCreditsForImplicitlyTerminated = false + + def code = UserStateChangeReasonCodes.NoSpecificChangeCode +} + +/** + * An authoritative calculation for the billing period. + * + * This marks a state for caching. + * + * @param billingMonthInfo + */ +case class MonthlyBillingCalculation(billingMonthInfo: BillingMonthInfo) extends UserStateChangeReason { + def shouldStoreUserState = true + + def shouldStoreCalculatedWalletEntries = true + + def forPreviousBillingMonth = MonthlyBillingCalculation(billingMonthInfo.previousMonth) + + def calculateCreditsForImplicitlyTerminated = true + + def code = UserStateChangeReasonCodes.MonthlyBillingCode +} + +/** + * Used for the realtime billing calculation. + * + * @param forWhenMillis The time this calculation is for + */ +case class RealtimeBillingCalculation(forWhenMillis: Long) extends UserStateChangeReason { + def shouldStoreUserState = false + + def shouldStoreCalculatedWalletEntries = false + + def forPreviousBillingMonth = this + + def calculateCreditsForImplicitlyTerminated = false + + def code = UserStateChangeReasonCodes.RealtimeBillingCode +} + +case class IMEventArrival(imEvent: IMEventModel) extends UserStateChangeReason { + def shouldStoreUserState = true + + def shouldStoreCalculatedWalletEntries = false + + def forPreviousBillingMonth = this + + def calculateCreditsForImplicitlyTerminated = false + + def code = UserStateChangeReasonCodes.IMEventArrivalCode +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala new file mode 100644 index 0000000..9d0e3cc --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReasonCodes.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2011-2012 GRNET S.A. All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and + * documentation are those of the authors and should not be + * interpreted as representing official policies, either expressed + * or implied, of GRNET S.A. + */ + +package gr.grnet.aquarium.computation.reason + +/** + * + * @author Christos KK Loverdos + */ + +object UserStateChangeReasonCodes { + type ChangeReasonCode = Int + + final val InitialSetupCode = 1 + final val NoSpecificChangeCode = 2 + final val MonthlyBillingCode = 3 + final val RealtimeBillingCode = 4 + final val IMEventArrivalCode = 5 +} diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala index b376bff..60726a8 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala @@ -261,7 +261,7 @@ trait Accounting extends DSLUtils with Loggable { newTotalAmount: Double, dslResource: DSLResource, defaultResourceMap: DSLResourcesMap, - agreementNamesByTimeslot: Map[Timeslot, String], + agreementNamesByTimeslot: SortedMap[Timeslot, String], algorithmCompiler: CostPolicyAlgorithmCompiler, policyStore: PolicyStore, clogOpt: Option[ContextualLogger] = None): (Timeslot, List[Chargeslot]) = { @@ -480,7 +480,7 @@ trait Accounting extends DSLUtils with Loggable { if (previousOccurred.getTime == event.occurredMillis) { dslResource.costPolicy match { case DiscreteCostPolicy => //Ok - case _ => return Some(List()) + case _ => return Just(List()) } } diff --git a/src/main/scala/gr/grnet/aquarium/store/UserStateStore.scala b/src/main/scala/gr/grnet/aquarium/store/UserStateStore.scala index 2760a9e..add6394 100644 --- a/src/main/scala/gr/grnet/aquarium/store/UserStateStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/UserStateStore.scala @@ -35,13 +35,12 @@ package gr.grnet.aquarium.store -import gr.grnet.aquarium.user.UserState -import com.ckkloverdos.maybe.Maybe +import gr.grnet.aquarium.computation.UserState /** * A store for user state snapshots. * - * This is used to hold snapshots of [[gr.grnet.aquarium.user.UserState]] + * This is used to hold snapshots of [[gr.grnet.aquarium.computation.UserState]] * * @author Christos KK Loverdos */ diff --git a/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala b/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala index 985a27e..8adb68c 100644 --- a/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala @@ -42,15 +42,13 @@ import scala.collection.JavaConversions._ import java.util.Date import collection.mutable.ConcurrentMap import java.util.concurrent.ConcurrentHashMap -import gr.grnet.aquarium.user.UserState import gr.grnet.aquarium.uid.ConcurrentVMLocalUIDGenerator -import gr.grnet.aquarium.{AquariumException, Configurable} +import gr.grnet.aquarium.Configurable import gr.grnet.aquarium.event.{WalletEntry, PolicyEntry} -import gr.grnet.aquarium.converter.JsonTextFormat -import gr.grnet.aquarium.util._ import gr.grnet.aquarium.event.im.{StdIMEvent, IMEventModel} import org.bson.types.ObjectId import gr.grnet.aquarium.event.resource.{StdResourceEvent, ResourceEventModel} +import gr.grnet.aquarium.computation.UserState /** * An implementation of various stores that persists data in memory. @@ -122,7 +120,7 @@ class MemStore extends UserStateStore goodOnes.sortWith { case (us1, us2) ⇒ - us1.oldestSnapshotTime > us2.oldestSnapshotTime + us1.occurredMillis > us2.occurredMillis } match { case head :: _ ⇒ Some(head) @@ -147,7 +145,7 @@ class MemStore extends UserStateStore goodOnes.sortWith { case (us1, us2) ⇒ - us1.oldestSnapshotTime > us2.oldestSnapshotTime + us1.occurredMillis > us2.occurredMillis } match { case head :: _ ⇒ Some(head) diff --git a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala index a8671ec..870fd13 100644 --- a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala @@ -36,8 +36,7 @@ package gr.grnet.aquarium.store.mongodb import com.mongodb.util.JSON -import gr.grnet.aquarium.user.UserState -import gr.grnet.aquarium.user.UserState.{JsonNames ⇒ UserStateJsonNames} +import gr.grnet.aquarium.computation.UserState.{JsonNames ⇒ UserStateJsonNames} import gr.grnet.aquarium.util.json.JsonSupport import collection.mutable.ListBuffer import gr.grnet.aquarium.event._ @@ -55,6 +54,7 @@ import org.bson.types.ObjectId import com.ckkloverdos.maybe.Maybe import gr.grnet.aquarium.util._ import gr.grnet.aquarium.converter.Conversions +import gr.grnet.aquarium.computation.UserState /** * Mongodb implementation of the various aquarium stores. @@ -378,9 +378,9 @@ object MongoDBStore { final val RESOURCE_EVENTS_COLLECTION = "resevents" /** - * Collection holding the snapshots of [[gr.grnet.aquarium.user.UserState]]. + * Collection holding the snapshots of [[gr.grnet.aquarium.computation.UserState]]. * - * [[gr.grnet.aquarium.user.UserState]] is held internally within [[gr.grnet.aquarium.actor.service.user .UserActor]]s. + * [[gr.grnet.aquarium.computation.UserState]] is held internally within [[gr.grnet.aquarium.actor.service.user.UserActor]]s. */ final val USER_STATES_COLLECTION = "userstates" diff --git a/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala b/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala deleted file mode 100644 index bd2d5b9..0000000 --- a/src/main/scala/gr/grnet/aquarium/user/UserDataSnapshot.scala +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright 2011-2012 GRNET S.A. All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and - * documentation are those of the authors and should not be - * interpreted as representing official policies, either expressed - * or implied, of GRNET S.A. - */ - -package gr.grnet.aquarium -package user - -import gr.grnet.aquarium.util.{findFromMapAsMaybe, findAndRemoveFromMap, shortClassNameOf} -import gr.grnet.aquarium.logic.accounting.Policy -import java.util.Date -import com.ckkloverdos.maybe.{NoVal, Maybe, Just} -import gr.grnet.aquarium.event.resource.ResourceEventModel.FullMutableResourceTypeMap -import logic.accounting.dsl.{Timeslot, DSLAgreement} -import collection.immutable.{TreeMap, SortedMap} -import util.date.MutableDateCalc -import event.resource.ResourceEventModel -import gr.grnet.aquarium.event.im.IMEventModel - -/** - * Snapshot of data that are user-related. - * - * @author Christos KK Loverdos - */ - -case class CreditSnapshot(creditAmount: Double, snapshotTime: Long) extends DataSnapshot - -case class IMStateSnapshot(imEvent: IMEventModel, snapshotTime: Long) extends DataSnapshot - -//case class RolesSnapshot(roles: List[String], snapshotTime: Long) extends DataSnapshot - -/** - * Represents an agreement valid for a specific amount of time. By convention, - * if an agreement is currently valid, then the validTo field is equal to `Long.MaxValue`. - */ -case class Agreement(name: String, validFrom: Long, validTo: Long = Long.MaxValue) { - assert(validTo > validFrom) - assert(!name.isEmpty) - -// Policy.policy(new Date(validFrom)) match { -// case Just(x) => x.findAgreement(agreement) match { -// case None => assert(false) -// case _ => -// } -// case _ => assert(false) -// } - - def timeslot = Timeslot(new Date(validFrom), new Date(validTo)) - - override def toString = - "Agreement(%s, %s, %s)". - format(name, new MutableDateCalc(validFrom), new MutableDateCalc(validTo)) -} - -/** - * All user agreements. The provided list of agreements cannot have time gaps. This - * is checked at object creation type. - */ -case class AgreementSnapshot(agreements: List[Agreement], snapshotTime: Long) extends DataSnapshot { - - ensureNoGaps(agreements.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false)) - - def agreementsByTimeslot: SortedMap[Timeslot, String] = { - TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*) - } - - def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match { - case ha :: (t @ (hb :: tail)) => - assert(ha.validTo - hb.validFrom == 1); - ensureNoGaps(t) - case h :: Nil => - assert(h.validTo == Long.MaxValue) - case Nil => () - } - - /** - * Get the user agreement at the specified timestamp - */ - def getAgreement(at: Long): Maybe[DSLAgreement] = - agreements.find{ x => x.validFrom < at && x.validTo > at} match { - case Some(x) => Policy.policy(new Date(at)).findAgreement(x.name) match { - case Some(z) => Just(z) - case None => NoVal - } - case None => NoVal - } - - override def toString = { - "%s(%s, %s)".format(shortClassNameOf(this), agreements, new MutableDateCalc(snapshotTime).toString) - } -} - -/** - * Maintains the current state of a resource instance owned by the user. - * The encoding is as follows: - * - * name: DSLResource.name - * instanceId: instance-id (in the resource's descriminatorField) - * data: current-resource-value - * snapshotTime: last-update-timestamp - * - * In order to have a uniform representation of the resource state for all - * resource types (complex or simple) the following convention applies: - * - * - If the resource is complex, the (name, instanceId) is (DSLResource.name, instance-id) - * - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1") - * - * @param resource Same as `resource` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]] - * @param instanceId Same as `instanceId` of [[gr.grnet.aquarium.event.resource.ResourceEventModel]] - * @param instanceAmount This is the amount kept for the resource instance. -* The general rule is that an amount saved in a [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]] - * represents a total value, while a value appearing in a [[gr.grnet.aquarium.event.resource.ResourceEventModel]] - * represents a difference. How these two values are combined to form the new amount is dictated - * by the underlying [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]] - * @param snapshotTime - * - * @author Christos KK Loverdos - */ -case class ResourceInstanceSnapshot(resource: String, - instanceId: String, - instanceAmount: Double, - snapshotTime: Long) extends DataSnapshot { - - def isSameResourceInstance(resource: String, instanceId: String) = { - this.resource == resource && - this.instanceId == instanceId - } -} - -/** - * A map from (resourceName, resourceInstanceId) to (value, snapshotTime). - * This representation is convenient for computations and updating, while the - * [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization. - * - * @author Christos KK Loverdos - */ -class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) { - def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot = - OwnedResourcesSnapshot( - resourcesMap map { - case ((name, instanceId), (value, snapshotTime)) ⇒ - ResourceInstanceSnapshot(name, instanceId, value, snapshotTime - )} toList, - snapshotTime - ) -} - -/** - * - * @param resourceInstanceSnapshots - * @param snapshotTime - * - * @author Christos KK Loverdos - */ -case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstanceSnapshot], snapshotTime: Long) - extends DataSnapshot { - - def toResourcesMap: OwnedResourcesMap = { - val tuples = for(rc <- resourceInstanceSnapshots) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime)) - - new OwnedResourcesMap(Map(tuples.toSeq: _*)) - } - - def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = { - // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible - // (at least out of the box). Thus, the update is O(L), where L is the length of the data List. - resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId)) - } - - def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = { - resourceInstanceSnapshots.find(x => resource == x.resource && instanceId == x.instanceId) - } - - def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = { - findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue) - } - - def computeResourcesSnapshotUpdate(resource: String, // resource name - instanceId: String, // resource instance id - newAmount: Double, - snapshotTime: Long): (OwnedResourcesSnapshot, - Option[ResourceInstanceSnapshot], - ResourceInstanceSnapshot) = { - - val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime) - val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId) - - val newResourceInstances = oldResourceInstanceOpt match { - case Some(oldResourceInstance) ⇒ - // Resource instance found, so delete the old one and add the new one - newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId) - case None ⇒ - // Resource not found, so this is the first time and we just add the new snapshot - newResourceInstance :: resourceInstanceSnapshots - } - - val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime) - - (newOwnedResources, oldResourceInstanceOpt, newResourceInstance) - } -} - - -/** - * A generic exception thrown when errors occur in dealing with user data snapshots - * - * @author Georgios Gousios - */ -class DataSnapshotException(msg: String) extends Exception(msg) - -/** - * Holds the user active/suspended status. - * - * @author Christos KK Loverdos - */ -//case class ActiveStateSnapshot(isActive: Boolean, snapshotTime: Long) extends DataSnapshot - -/** - * Keeps the latest resource event per resource instance. - * - * @param resourceEvents - * @param snapshotTime - * - * @author Christos KK Loverdos - */ -case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel], - snapshotTime: Long) extends DataSnapshot { - - /** - * The gateway to playing with mutable state. - * - * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsWorker]]. - */ - def toMutableWorker = { - val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() - for(latestEvent <- resourceEvents) { - map(latestEvent.fullResourceInfo) = latestEvent - } - LatestResourceEventsWorker(map) - } -} - -/** - * This is the mutable cousin of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]]. - * - * @param latestEventsMap - * - * @author Christos KK Loverdos - */ -case class LatestResourceEventsWorker(latestEventsMap: FullMutableResourceTypeMap) { - - /** - * The gateway to immutable state. - * - * @param snapshotTime The relevant snapshot time. - * @return A fresh instance of [[gr.grnet.aquarium.user.LatestResourceEventsSnapshot]]. - */ - def toImmutableSnapshot(snapshotTime: Long) = - LatestResourceEventsSnapshot(latestEventsMap.valuesIterator.toList, snapshotTime) - - def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = { - latestEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent - } - - def findResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { - latestEventsMap.get((resource, instanceId)) - } - - def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { - findAndRemoveFromMap(latestEventsMap, (resource, instanceId)) - } - - def size = latestEventsMap.size - - def foreach[U](f: ResourceEventModel => U): Unit = { - latestEventsMap.valuesIterator.foreach(f) - } -} - -object LatestResourceEventsWorker { - final val Empty = LatestResourceEventsWorker(scala.collection.mutable.Map()) - - /** - * Helper factory to construct a worker from a list of events. - */ - def fromList(latestEventsList: List[ResourceEventModel]): LatestResourceEventsWorker = { - LatestResourceEventsSnapshot(latestEventsList, 0L).toMutableWorker - } -} - -/** - * Keeps the implicit OFF events when a billing period ends. - * This is normally recorded in the [[gr.grnet.aquarium.user.UserState]]. - * - * @param implicitlyIssuedEvents - * @param snapshotTime - * - * @author Christos KK Loverdos - */ -case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[ResourceEventModel], - snapshotTime: Long) extends DataSnapshot { - /** - * The gateway to playing with mutable state. - * - * @return A fresh instance of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsWorker]]. - */ - def toMutableWorker = { - val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() - for(implicitEvent <- implicitlyIssuedEvents) { - map(implicitEvent.fullResourceInfo) = implicitEvent - } - - ImplicitlyIssuedResourceEventsWorker(map) - } -} - -/** - * This is the mutable cousin of [[gr.grnet.aquarium.user.ImplicitlyIssuedResourceEventsSnapshot]]. - * - * @param implicitlyIssuedEventsMap - * - * @author Christos KK Loverdos - */ -case class ImplicitlyIssuedResourceEventsWorker(implicitlyIssuedEventsMap: FullMutableResourceTypeMap) { - - def toList: scala.List[ResourceEventModel] = { - implicitlyIssuedEventsMap.valuesIterator.toList - } - - def toImmutableSnapshot(snapshotTime: Long) = - ImplicitlyIssuedResourceEventsSnapshot(toList, snapshotTime) - - def findAndRemoveResourceEvent(resource: String, instanceId: String): Option[ResourceEventModel] = { - findAndRemoveFromMap(implicitlyIssuedEventsMap, (resource, instanceId)) - } - - def size = implicitlyIssuedEventsMap.size - - def foreach[U](f: ResourceEventModel => U): Unit = { - implicitlyIssuedEventsMap.valuesIterator.foreach(f) - } -} - -object ImplicitlyIssuedResourceEventsWorker { - final val Empty = ImplicitlyIssuedResourceEventsWorker(scala.collection.mutable.Map()) -} - -/** - * - * @author Christos KK Loverdos - * - * @param ignoredFirstEvents - * @param snapshotTime - */ -case class IgnoredFirstResourceEventsSnapshot(ignoredFirstEvents: List[ResourceEventModel], - snapshotTime: Long) extends DataSnapshot { - def toMutableWorker = { - val map = scala.collection.mutable.Map[ResourceEventModel.FullResourceType, ResourceEventModel]() - for(ignoredFirstEvent <- ignoredFirstEvents) { - map(ignoredFirstEvent.fullResourceInfo) = ignoredFirstEvent - } - - IgnoredFirstResourceEventsWorker(map) - } -} - -/** - * - * @author Christos KK Loverdos - * @param ignoredFirstEventsMap - */ -case class IgnoredFirstResourceEventsWorker(ignoredFirstEventsMap: FullMutableResourceTypeMap) { - def toImmutableSnapshot(snapshotTime: Long) = - IgnoredFirstResourceEventsSnapshot(ignoredFirstEventsMap.valuesIterator.toList, snapshotTime) - - def findAndRemoveResourceEvent(resource: String, instanceId: String): Maybe[ResourceEventModel] = { - findAndRemoveFromMap(ignoredFirstEventsMap, (resource, instanceId)) - } - - def updateResourceEvent(resourceEvent: ResourceEventModel): Unit = { - ignoredFirstEventsMap((resourceEvent.resource, resourceEvent.instanceID)) = resourceEvent - } - - def size = ignoredFirstEventsMap.size - - def foreach[U](f: ResourceEventModel => U): Unit = { - ignoredFirstEventsMap.valuesIterator.foreach(f) - } -} - -object IgnoredFirstResourceEventsWorker { - final val Empty = IgnoredFirstResourceEventsWorker(scala.collection.mutable.Map()) -} \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala b/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala index 47b2971..d57a9b2 100644 --- a/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala +++ b/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala @@ -36,7 +36,7 @@ package gr.grnet.aquarium.util import org.slf4j.Logger -import com.ckkloverdos.maybe.{Failed, Just, Maybe} +import com.ckkloverdos.maybe.Failed /** * A logger that keeps track of working context and indentation level. @@ -46,58 +46,50 @@ import com.ckkloverdos.maybe.{Failed, Just, Maybe} * A sample output follows: * * {{{ - [DEBUG] 2012-02-13 12:54:53,653 main - doFullMonthlyBilling(2012-01) BEGIN - [DEBUG] 2012-02-13 12:54:53,653 main - findUserStateAtEndOfBillingMonth(2011-12) BEGIN - [DEBUG] 2012-02-13 12:54:53,661 main - findUserStateAtEndOfBillingMonth(2011-12) Found 0 out of sync events, will have to (re)compute user state - [DEBUG] 2012-02-13 12:54:53,661 main - findUserStateAtEndOfBillingMonth(2011-12) Computing full month billing - [DEBUG] 2012-02-13 12:54:53,662 main - doFullMonthlyBilling(2011-12) BEGIN - [DEBUG] 2012-02-13 12:54:53,662 main - findUserStateAtEndOfBillingMonth(2011-11) BEGIN - [DEBUG] 2012-02-13 12:54:53,663 main - findUserStateAtEndOfBillingMonth(2011-11) Found 0 out of sync events, will have to (re)compute user state - [DEBUG] 2012-02-13 12:54:53,663 main - findUserStateAtEndOfBillingMonth(2011-11) Computing full month billing - [DEBUG] 2012-02-13 12:54:53,664 main - doFullMonthlyBilling(2011-11) BEGIN - [DEBUG] 2012-02-13 12:54:53,664 main - findUserStateAtEndOfBillingMonth(2011-10) BEGIN - [DEBUG] 2012-02-13 12:54:53,667 main - findUserStateAtEndOfBillingMonth(2011-10) User did not exist before 2011-11-01 00:00:00.000. Returning UserState(Christos,0,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(Map(),0),List(),List(),LatestResourceEventsSnapshot(Map(),0),0,ActiveStateSnapshot(false,0),CreditSnapshot(0.0,0),AgreementSnapshot(List(Agreement(default,0,-1)),0),RolesSnapshot(List(),0),OwnedResourcesSnapshot(List(),0)) - [DEBUG] 2012-02-13 12:54:53,668 main - findUserStateAtEndOfBillingMonth(2011-10) END - [DEBUG] 2012-02-13 12:54:53,672 main - doFullMonthlyBilling(2011-11) previousResourceEvents = LatestResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,673 main - doFullMonthlyBilling(2011-11) theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,680 main - doFullMonthlyBilling(2011-11) resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0)) - [DEBUG] 2012-02-13 12:54:53,681 main - doFullMonthlyBilling(2011-11) Found 0 resource events, starting processing... - [DEBUG] 2012-02-13 12:54:53,683 main - doFullMonthlyBilling(2011-11) END - [DEBUG] 2012-02-13 12:54:53,683 main - findUserStateAtEndOfBillingMonth(2011-11) END - [DEBUG] 2012-02-13 12:54:53,684 main - doFullMonthlyBilling(2011-12) previousResourceEvents = LatestResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,684 main - doFullMonthlyBilling(2011-12) theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,685 main - doFullMonthlyBilling(2011-12) resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0)) - [DEBUG] 2012-02-13 12:54:53,686 main - doFullMonthlyBilling(2011-12) Found 0 resource events, starting processing... - [DEBUG] 2012-02-13 12:54:53,686 main - doFullMonthlyBilling(2011-12) END - [DEBUG] 2012-02-13 12:54:53,687 main - findUserStateAtEndOfBillingMonth(2011-12) END - [DEBUG] 2012-02-13 12:54:53,687 main - doFullMonthlyBilling(2012-01) previousResourceEvents = LatestResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,688 main - doFullMonthlyBilling(2012-01) theImplicitOFFs = ImplicitlyIssuedResourceEventsWorker(Map()) - [DEBUG] 2012-02-13 12:54:53,688 main - doFullMonthlyBilling(2012-01) resourceEventStore = MemStore(Map(UserState -> 0, WalletEntry -> 0, ResourceEvent -> 5, PolicyEntry -> 0, IMEvent -> 0)) - [DEBUG] 2012-02-13 12:54:53,689 main - doFullMonthlyBilling(2012-01) Found 4 resource events, starting processing... - [DEBUG] 2012-02-13 12:54:53,690 main - doFullMonthlyBilling(2012-01) Processing EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos) - [DEBUG] 2012-02-13 12:54:53,691 main - doFullMonthlyBilling(2012-01) 0 previousResourceEvents - [DEBUG] 2012-02-13 12:54:53,691 main - doFullMonthlyBilling(2012-01) 0 theImplicitOFFs - [DEBUG] 2012-02-13 12:54:53,694 main - doFullMonthlyBilling(2012-01) Processing: ResourceEvent(2,1325379600000,1325379600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,99.0,Map()) - [DEBUG] 2012-02-13 12:54:53,704 main - doFullMonthlyBilling(2012-01) Processing EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo) - [DEBUG] 2012-02-13 12:54:53,705 main - doFullMonthlyBilling(2012-01) 1 previousResourceEvents - [DEBUG] 2012-02-13 12:54:53,708 main - doFullMonthlyBilling(2012-01) EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos) - [DEBUG] 2012-02-13 12:54:53,709 main - doFullMonthlyBilling(2012-01) 0 theImplicitOFFs - [DEBUG] 2012-02-13 12:54:53,709 main - doFullMonthlyBilling(2012-01) Ignoring not billable EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo) - [DEBUG] 2012-02-13 12:54:53,711 main - doFullMonthlyBilling(2012-01) Processing EVENT(3, [2012-01-02 04:00:00.000], 23.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos) - [DEBUG] 2012-02-13 12:54:53,712 main - doFullMonthlyBilling(2012-01) 2 previousResourceEvents - [DEBUG] 2012-02-13 12:54:53,713 main - doFullMonthlyBilling(2012-01) EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo) - [DEBUG] 2012-02-13 12:54:53,714 main - doFullMonthlyBilling(2012-01) EVENT(2, [2012-01-01 03:00:00.000], 99.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos) - [DEBUG] 2012-02-13 12:54:53,714 main - doFullMonthlyBilling(2012-01) 0 theImplicitOFFs - [DEBUG] 2012-02-13 12:54:53,715 main - doFullMonthlyBilling(2012-01) Processing: ResourceEvent(3,1325469600000,1325469600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,23.0,Map()) - [DEBUG] 2012-02-13 12:54:53,716 main - doFullMonthlyBilling(2012-01) Previous : ResourceEvent(2,1325379600000,1325379600000,Christos,pithos,diskspace,pithos/diskspace/DISK.1,1.0,99.0,Map()) - [DEBUG] 2012-02-13 12:54:53,718 main - doFullMonthlyBilling(2012-01) Processing EVENT(1, [2012-01-02 10:00:00.000], OFF, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo) - [DEBUG] 2012-02-13 12:54:53,719 main - doFullMonthlyBilling(2012-01) 2 previousResourceEvents - [DEBUG] 2012-02-13 12:54:53,719 main - doFullMonthlyBilling(2012-01) EVENT(0, [2012-01-02 01:00:00.000], ON, vmtime::synnefo/vmtime/VM.1, Map(), Christos, synnefo) - [DEBUG] 2012-02-13 12:54:53,721 main - doFullMonthlyBilling(2012-01) EVENT(3, [2012-01-02 04:00:00.000], 23.0 [MB/Hr], diskspace::pithos/diskspace/DISK.1, Map(), Christos, pithos) - [DEBUG] 2012-02-13 12:54:53,721 main - doFullMonthlyBilling(2012-01) 0 theImplicitOFFs - [DEBUG] 2012-02-13 12:54:53,722 main - doFullMonthlyBilling(2012-01) Processing: ResourceEvent(1,1325491200000,1325491200000,Christos,synnefo,vmtime,synnefo/vmtime/VM.1,1.0,0.0,Map()) - [DEBUG] 2012-02-13 12:54:53,735 main - doFullMonthlyBilling(2012-01) Previous : ResourceEvent(0,1325458800000,1325458800000,Christos,synnefo,vmtime,synnefo/vmtime/VM.1,1.0,1.0,Map()) - [DEBUG] 2012-02-13 12:54:53,736 main - doFullMonthlyBilling(2012-01) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() +++ [Events by OccurredMillis] +++ +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() --- [Events by OccurredMillis] --- +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2012-01) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) No user state found from cache, will have to (re)compute +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-12) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) No user state found from cache, will have to (re)compute +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-11) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) BEGIN +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) User did not exist before 2011-11-01 00:00:00.000 +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) Returning INITIAL state [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,InitialUserStateSetup,0,None,4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-11) calculationReason = MonthlyBillingCalculation(2011-11) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-11) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-11) RETURN UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-11) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-12) calculationReason = MonthlyBillingCalculation(2011-12) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-12) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-12) RETURN UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2011-12) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) +++ [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] +++ +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) Cost policy OnOffCostPolicy for DSLResource(vmtime,Hr,OnOffCostPolicy,true,instanceId) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) PreviousM None +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) Ignoring first event of its kind EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) --- [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] --- +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2012-01) calculationReason = MonthlyBillingCalculation(2012-01) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2012-01) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2012-01) RETURN UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementsSnapshot(List(AgreementSnapshot(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillingCalculation(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - doFullMonthlyBilling(2012-01) END +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() _id = 4fa7e12ba0eee3db73fbe8d0 +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() parentId = Some(4fa7e12ba0eee3db73fbe8d0) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() credits = 0.0 +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() changeReason = MonthlyBillingCalculation(2012-01) +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() implicitlyIssued [#=0] = List() +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() latestResourceEvents [#=1]: +DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo) +DEBUG 17:50:20 g.g.a.user.UserStateComputationsTest - testOrphanOFF() newWalletEntries [#=0] = List() * }}} * * @author Christos KK Loverdos diff --git a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala index 615424f..133b292 100644 --- a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala +++ b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala @@ -39,13 +39,15 @@ import gr.grnet.aquarium.store.memory.MemStore import gr.grnet.aquarium.util.date.MutableDateCalc import gr.grnet.aquarium.logic.accounting.dsl._ import gr.grnet.aquarium.logic.accounting.{Policy, Accounting} -import gr.grnet.aquarium.util.{Loggable, ContextualLogger, justForSure} +import gr.grnet.aquarium.util.{Loggable, ContextualLogger} import gr.grnet.aquarium.simulation._ import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator} -import com.ckkloverdos.maybe.{Maybe, Just, NoVal} +import com.ckkloverdos.maybe.{Maybe, Just} import org.junit.{Assert, Ignore, Test} -import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler, SimpleCostPolicyAlgorithmCompiler} +import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler} import gr.grnet.aquarium.{AquariumException, Configurator} +import gr.grnet.aquarium.computation.{UserState, BillingMonthInfo, UserStateComputations} +import gr.grnet.aquarium.computation.reason.MonthlyBillingCalculation /** @@ -240,7 +242,7 @@ aquariumpolicy: val UserCKKL = Aquarium.newUser("CKKL", UserCreationDate) - val InitialUserState = Computations.createInitialUserState( + val InitialUserState = UserState.createInitialUserState( userID = UserCKKL.userId, userCreationMillis = UserCreationDate.getTime, isActive = true, -- 1.7.10.4