From c11b8ebcbf64910d7c6725b3cdc8b733363c2eda Mon Sep 17 00:00:00 2001 From: Christos KK Loverdos Date: Mon, 28 May 2012 16:39:40 +0300 Subject: [PATCH] WIP Resource event handling --- src/main/resources/aquarium.properties | 2 - src/main/scala/gr/grnet/aquarium/Aquarium.scala | 35 +-- .../aquarium/actor/service/user/UserActor.scala | 117 +++---- .../grnet/aquarium/computation/NewUserState.scala | 6 +- .../gr/grnet/aquarium/computation/UserState.scala | 126 ++------ .../computation/UserStateComputations.scala | 57 +--- .../aquarium/computation/UserStateWorker.scala | 4 +- .../computation/data/AgreementHistory.scala | 36 ++- .../ImplicitlyIssuedResourceEventsSnapshot.scala | 4 + .../data/LatestResourceEventsSnapshot.scala | 3 + .../computation/data/OwnedResourcesSnapshot.scala | 4 + .../aquarium/computation/data/RoleHistory.scala | 37 ++- .../aquarium/logic/accounting/Accounting.scala | 328 +------------------- .../accounting/Chargeslot.scala} | 29 +- .../grnet/aquarium/logic/accounting/Policy.scala | 6 +- .../gr/grnet/aquarium/store/StoreProvider.scala | 1 - .../gr/grnet/aquarium/store/WalletEntryStore.scala | 71 ----- .../gr/grnet/aquarium/store/memory/MemStore.scala | 65 +--- .../aquarium/store/mongodb/MongoDBStore.scala | 106 +------ .../store/mongodb/MongoDBStoreProvider.scala | 1 - src/main/scala/gr/grnet/aquarium/util/Tags.scala | 1 + src/test/resources/aquarium.properties | 3 - .../grnet/aquarium/logic/test/AccountingTest.scala | 66 +--- .../aquarium/user/UserStateComputationsTest.scala | 39 +-- 24 files changed, 274 insertions(+), 873 deletions(-) rename src/main/scala/gr/grnet/aquarium/{computation/DefaultUserStateComputations.scala => logic/accounting/Chargeslot.scala} (68%) delete mode 100644 src/main/scala/gr/grnet/aquarium/store/WalletEntryStore.scala diff --git a/src/main/resources/aquarium.properties b/src/main/resources/aquarium.properties index 213accf..76e7133 100644 --- a/src/main/resources/aquarium.properties +++ b/src/main/resources/aquarium.properties @@ -88,8 +88,6 @@ store.provider.class=gr.grnet.aquarium.store.mongodb.MongoDBStoreProvider # Override the user store (if present, it will not be given by the store provider above) #user.state.store.class=gr.grnet.aquarium.store.memory.MemStorede the event store (if present, it will not be given by the store provider above) #resource.event.store.class= -# Override the WalletEntry store (if present, it will not be given by the store provider above) -#wallet.entry.store.class= # Override the user event store (if present, it will not be given by the store provider above) #user.event.store.class= # Override the user event store (if present, it will not be given by the store provider above) diff --git a/src/main/scala/gr/grnet/aquarium/Aquarium.scala b/src/main/scala/gr/grnet/aquarium/Aquarium.scala index efa0b1b..9733ec3 100644 --- a/src/main/scala/gr/grnet/aquarium/Aquarium.scala +++ b/src/main/scala/gr/grnet/aquarium/Aquarium.scala @@ -49,6 +49,8 @@ import gr.grnet.aquarium.converter.StdConverters import java.util.concurrent.atomic.AtomicBoolean import gr.grnet.aquarium.ResourceLocator._ import com.ckkloverdos.sys.SysProp +import gr.grnet.aquarium.computation.UserStateComputations +import gr.grnet.aquarium.logic.accounting.algorithm.{SimpleCostPolicyAlgorithmCompiler, CostPolicyAlgorithmCompiler} /** * This is the Aquarium entry point. @@ -120,6 +122,11 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable { } + private[this] lazy val _algorithmCompiler: CostPolicyAlgorithmCompiler = SimpleCostPolicyAlgorithmCompiler + + // FIXME: () ⇒ this ? + private[this] lazy val _userStateComputations = new UserStateComputations(() ⇒ this) + private[this] lazy val _actorProvider = newInstance[RoleableActorProviderService](props(Keys.actor_provider_class)) /** @@ -162,18 +169,6 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable { } } - private[this] lazy val _WalletEventStoreM: Maybe[WalletEntryStore] = { - // If there is a specific `IMStore` implementation specified in the - // properties, then this implementation overrides the event store given by - // `IMProvider`. - props.get(Keys.wallet_entry_store_class) map { - className ⇒ - val instance = newInstance[WalletEntryStore](className) - logger.info("Overriding WalletEntryStore provisioning. Implementation given by: %s".format(instance.getClass)) - instance - } - } - private[this] lazy val _policyStoreM: Maybe[PolicyStore] = { props.get(Keys.policy_store_class) map { className ⇒ @@ -346,6 +341,10 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable { stopServices() } + def algorithmCompiler = _algorithmCompiler + + def userStateComputations = _userStateComputations + def converters = _converters def actorProvider = _actorProvider @@ -368,13 +367,6 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable { } } - def walletStore = { - _WalletEventStoreM match { - case Just(es) ⇒ es - case _ ⇒ storeProvider.walletEntryStore - } - } - def imEventStore = { _imEventStoreM match { case Just(es) ⇒ es @@ -521,11 +513,6 @@ object Aquarium { /** * The class that implements the wallet entries store */ - final val wallet_entry_store_class = "wallet.entry.store.class" - - /** - * The class that implements the wallet entries store - */ final val policy_store_class = "policy.store.class" 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 f8fbde4..12a2c08 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 @@ -41,11 +41,13 @@ import gr.grnet.aquarium.actor._ import akka.config.Supervision.Temporary import gr.grnet.aquarium.Aquarium +import gr.grnet.aquarium.util.{shortClassNameOf, shortNameOfClass} import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent} -import gr.grnet.aquarium.actor.message.{GetUserStateRequest, GetUserBalanceRequest} import gr.grnet.aquarium.computation.data.IMStateSnapshot import gr.grnet.aquarium.event.model.im.IMEventModel import gr.grnet.aquarium.actor.message.config.{InitializeUserState, ActorProviderConfigured, AquariumPropertiesLoaded} +import gr.grnet.aquarium.computation.NewUserState +import gr.grnet.aquarium.actor.message.{GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest} /** * @@ -53,24 +55,24 @@ import gr.grnet.aquarium.actor.message.config.{InitializeUserState, ActorProvide */ class UserActor extends ReflectiveRoleableActor { + private[this] var _userID: String = "" private[this] var _imState: IMStateSnapshot = _ // private[this] var _userState: UserState = _ -// private[this] var _newUserState: NewUserState = _ + private[this] var _newUserState: NewUserState = _ self.lifeCycle = Temporary -// private[this] def _userID = this._newUserState.userID private[this] def _shutmedown(): Unit = { -// if(_haveUserState) { -// UserActorCache.invalidate(_userID) -// } + if(_haveUserState) { + UserActorCache.invalidate(_userID) + } self.stop() } override protected def onThrowable(t: Throwable, message: AnyRef) = { logChainOfCauses(t) -// ERROR(t, "Terminating due to: %s(%s)", shortClassNameOf(t), t.getMessage) + ERROR(t, "Terminating due to: %s(%s)", shortClassNameOf(t), t.getMessage) _shutmedown() } @@ -83,9 +85,9 @@ class UserActor extends ReflectiveRoleableActor { aquarium.props.getLong(Aquarium.Keys.user_state_timestamp_threshold).getOr(10000) -// private[this] def _haveUserState = { -// this._newUserState ne null -// } + private[this] def _haveUserState = { + this._newUserState ne null + } private[this] def _haveIMState = { this._imState ne null @@ -115,12 +117,19 @@ class UserActor extends ReflectiveRoleableActor { this._imState = newState } - logger.debug("Recomputed %s".format(this._imState)) + DEBUG("Recomputed %s = %s", shortNameOfClass(classOf[IMStateSnapshot]), this._imState) + } + + private[this] def createUserState(userID: String): Unit = { } def onInitializeUserState(event: InitializeUserState): Unit = { - logger.debug("Got %s".format(event)) - createIMState(event.userID) + val userID = event.userID + this._userID = userID + DEBUG("Got %s", event) + + createIMState(userID) + createUserState(userID) } private[this] def _getAgreementNameForNewUser(imEvent: IMEventModel): String = { @@ -135,25 +144,36 @@ class UserActor extends ReflectiveRoleableActor { */ def onProcessIMEvent(processEvent: ProcessIMEvent): Unit = { val imEvent = processEvent.imEvent - val hadIMState = _haveIMState - - if(hadIMState) { - if(this._imState.latestIMEvent.id == imEvent.id) { - // This happens when the actor is brought to life, then immediately initialized, and then - // sent the first IM event. But from the initialization procedure, this IM event will have - // already been loaded from DB! - logger.debug("Ignoring first %s after birth".format(imEvent.toDebugString)) - return - } - this._imState = this._imState.copyWithEvent(imEvent) - } else { - this._imState = IMStateSnapshot.initial(imEvent) + if(!_haveIMState) { + // This is an error. Should have been initialized from somewhere ... + throw new Exception("Got %s while being uninitialized".format(processEvent)) } -// DEBUG("%s %s = %s", if(hadIMState) "Update" else "Set", shortClassNameOf(this._imState), this._imState) + if(this._imState.latestIMEvent.id == imEvent.id) { + // This happens when the actor is brought to life, then immediately initialized, and then + // sent the first IM event. But from the initialization procedure, this IM event will have + // already been loaded from DB! + DEBUG("Ignoring first %s after birth", imEvent.toDebugString) + return + } + + this._imState = this._imState.copyWithEvent(imEvent) + + DEBUG("Update %s = %s", shortClassNameOf(this._imState), this._imState) + } + + def onProcessResourceEvent(event: ProcessResourceEvent): Unit = { + val rcEvent = event.rcEvent + + if(!_haveIMState) { + // This means the user has not been activated. So, we do not process any resource event + INFO("Not processing %s", rcEvent.toJsonString) + return + } } + def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = { val userId = event.userID // FIXME: Implement @@ -166,35 +186,22 @@ class UserActor extends ReflectiveRoleableActor { // self reply GetUserStateResponse(userId, Right(this._userState)) } - def onProcessResourceEvent(event: ProcessResourceEvent): Unit = { - val rcEvent = event.rcEvent - - logger.info("Got\n{}", rcEvent.toJsonString) + private[this] def D_userID = { + this._userID } + private[this] def DEBUG(fmt: String, args: Any*) = + logger.debug("User[%s]: %s".format(D_userID, fmt.format(args: _*))) + + private[this] def INFO(fmt: String, args: Any*) = + logger.info("User[%s]: %s".format(D_userID, fmt.format(args: _*))) + + private[this] def WARN(fmt: String, args: Any*) = + logger.warn("User[%s]: %s".format(D_userID, fmt.format(args: _*))) + + private[this] def ERROR(fmt: String, args: Any*) = + logger.error("User[%s]: %s".format(D_userID, fmt.format(args: _*))) -// private[this] def D_userID = { -// if(this._newUserState eq null) -// if(this._imState eq null) -// "" -// else -// this._imState.latestIMEvent.userID -// else -// this._newUserState.userID -// } -// -// private[this] def DEBUG(fmt: String, args: Any*) = -// logger.debug("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*))) -// -// private[this] def INFO(fmt: String, args: Any*) = -// logger.info("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*))) -// -// private[this] def WARN(fmt: String, args: Any*) = -// logger.warn("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*))) -// -// private[this] def ERROR(fmt: String, args: Any*) = -// logger.error("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*))) -// -// private[this] def ERROR(t: Throwable, fmt: String, args: Any*) = -// logger.error("UserActor[%s]: %s".format(D_userID, fmt.format(args: _*)), t) + private[this] def ERROR(t: Throwable, fmt: String, args: Any*) = + logger.error("User[%s]: %s".format(D_userID, fmt.format(args: _*)), t) } diff --git a/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala b/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala index 29f0a8e..34086bc 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala @@ -51,7 +51,8 @@ case class NewUserState( stateReferenceMillis: Long, // The time this state refers to totalCredits: Double, roleHistory: RoleHistory, - agreementHistory: AgreementHistory + agreementHistory: AgreementHistory, + latestResourceEventID: String ) object NewUserState { @@ -77,7 +78,8 @@ object NewUserState { userCreationMillis, credits, RoleHistory.initial(role, userCreationMillis), - AgreementHistory.initial(agreement, userCreationMillis) + AgreementHistory.initial(agreement, userCreationMillis), + "" ) } } \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserState.scala b/src/main/scala/gr/grnet/aquarium/computation/UserState.scala index eee1c1d..2977dfa 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/UserState.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserState.scala @@ -37,14 +37,12 @@ package gr.grnet.aquarium.computation import org.bson.types.ObjectId -import gr.grnet.aquarium.AquariumInternalError import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters} -import gr.grnet.aquarium.event.model.{WalletEntry, NewWalletEntry} +import gr.grnet.aquarium.event.model.NewWalletEntry import gr.grnet.aquarium.util.json.JsonSupport import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement -import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup, IMEventArrival} -import gr.grnet.aquarium.event.model.im.{StdIMEvent, IMEventModel} -import gr.grnet.aquarium.computation.data.{RoleHistory, AgreementHistoryItem, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementHistory, CreditSnapshot, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot, IMStateSnapshot} +import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup} +import gr.grnet.aquarium.computation.data.{RoleHistory, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementHistory, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot} /** * A comprehensive representation of the User's state. @@ -70,20 +68,15 @@ import gr.grnet.aquarium.computation.data.{RoleHistory, AgreementHistoryItem, Re * @param isFullBillingMonthState * @param theFullBillingMonth * @param implicitlyIssuedSnapshot - * @param billingMonthWalletEntries - * @param outOfSyncWalletEntries * @param latestResourceEventsSnapshot - * @param billingPeriodResourceEventsCounter * @param billingPeriodOutOfSyncResourceEventsCounter - * @param creditsSnapshot - * @param agreementsSnapshot + * @param agreementHistory * @param ownedResourcesSnapshot * @param newWalletEntries * 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.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 * this user state. @@ -129,45 +122,33 @@ case class UserState( implicitlyIssuedSnapshot: ImplicitlyIssuedResourceEventsSnapshot, /** - * So far computed wallet entries for the current billing month. - */ - billingMonthWalletEntries: List[WalletEntry], - - /** - * Wallet entries that were computed for out of sync events. - * (for the current billing month ??) - */ - outOfSyncWalletEntries: List[WalletEntry], - - /** * The latest (previous) resource events per resource instance. */ latestResourceEventsSnapshot: LatestResourceEventsSnapshot, /** - * Counts the total number of resource events used to produce this user state for - * the billing period recorded by `billingPeriodSnapshot` - */ - billingPeriodResourceEventsCounter: Long, - - /** * The out of sync events used to produce this user state for * the billing period recorded by `billingPeriodSnapshot` */ billingPeriodOutOfSyncResourceEventsCounter: Long, - imStateSnapshot: IMStateSnapshot, - creditsSnapshot: CreditSnapshot, - agreementsSnapshot: AgreementHistory, + + totalCredits: Double, + + roleHistory: RoleHistory, + + agreementHistory: AgreementHistory, + 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, // The user state we used to compute this one. Normally the (cached) // state at the beginning of the billing period. parentUserStateId: Option[String] = None, - _id: ObjectId = new ObjectId() + _id: String = new ObjectId().toString ) extends JsonSupport { def idOpt: Option[String] = _id match { @@ -180,7 +161,7 @@ case class UserState( // def userCreationFormatedDate = new MutableDateCalc(userCreationMillis).toString def findDSLAgreementForTime(at: Long): Option[DSLAgreement] = { - agreementsSnapshot.findForTime(at) + agreementHistory.findForTime(at) } def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = { @@ -236,46 +217,11 @@ object UserState { final val userID = "userID" } - 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 - - UserState( - true, - userID, - userCreationMillis, - 0L, - false, - null, - ImplicitlyIssuedResourceEventsSnapshot(List()), - Nil, - Nil, - LatestResourceEventsSnapshot(List()), - 0L, - 0L, - IMStateSnapshot(true, imEvent, RoleHistory.Empty), - CreditSnapshot(credits), - AgreementHistory(List(AgreementHistoryItem(agreementName, userCreationMillis))), - OwnedResourcesSnapshot(Nil), - Nil, - userCreationMillis, - InitialUserStateSetup - ) - } - def createInitialUserState(userID: String, userCreationMillis: Long, - isActive: Boolean, - credits: Double, - roleNames: List[String] = List(), - agreementName: String = DSLAgreement.DefaultAgreementName) = { - val now = userCreationMillis - + totalCredits: Double, + initialRole: String, + initialAgreement: String) = { UserState( true, userID, @@ -283,36 +229,26 @@ object UserState { 0L, false, null, - ImplicitlyIssuedResourceEventsSnapshot(List()), - Nil, - Nil, - LatestResourceEventsSnapshot(List()), - 0L, + ImplicitlyIssuedResourceEventsSnapshot.Empty, + LatestResourceEventsSnapshot.Empty, 0L, - IMStateSnapshot( - true, - StdIMEvent( - "", - now, now, userID, - "", - isActive, roleNames.headOption.getOrElse("default"), - "1.0", - IMEventModel.EventTypeNames.create, Map()), - RoleHistory.Empty - ), - CreditSnapshot(credits), - AgreementHistory(List(AgreementHistoryItem(agreementName, userCreationMillis))), - OwnedResourcesSnapshot(Nil), + totalCredits, + RoleHistory.initial(initialRole, userCreationMillis), + AgreementHistory.initial(initialAgreement, userCreationMillis), + OwnedResourcesSnapshot.Empty, Nil, - now, + userCreationMillis, InitialUserStateSetup ) } def createInitialUserStateFrom(us: UserState): UserState = { createInitialUserState( - us.imStateSnapshot.latestIMEvent, - us.creditsSnapshot.creditAmount, - us.agreementsSnapshot.agreementsByTimeslot.valuesIterator.toList.last) + us.userID, + us.userCreationMillis, + us.totalCredits, + us.roleHistory.firstRoleName.getOrElse("default"), // FIXME What is the default? + us.agreementHistory.firstAgreementName.getOrElse("default") // FIXME What is the default? + ) } } diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala index 7f0a5bf..3545449 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala @@ -36,31 +36,34 @@ package gr.grnet.aquarium.computation import scala.collection.mutable -import gr.grnet.aquarium.{AquariumInternalError, AquariumException} import gr.grnet.aquarium.util.{ContextualLogger, Loggable} import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc} import gr.grnet.aquarium.logic.accounting.dsl.DSLResourcesMap import gr.grnet.aquarium.logic.accounting.Accounting -import gr.grnet.aquarium.logic.accounting.algorithm.CostPolicyAlgorithmCompiler -import gr.grnet.aquarium.store.{StoreProvider, PolicyStore} import gr.grnet.aquarium.computation.data._ import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason} import gr.grnet.aquarium.event.model.NewWalletEntry import gr.grnet.aquarium.event.model.resource.ResourceEventModel +import gr.grnet.aquarium.{Aquarium, AquariumInternalError, AquariumException} /** * * @author Christos KK Loverdos */ -class UserStateComputations extends Loggable { +class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable { + + protected lazy val aquarium = _aquarium() + protected lazy val storeProvider = aquarium.storeProvider + protected lazy val accounting = new Accounting {} + protected lazy val algorithmCompiler = aquarium.algorithmCompiler + protected lazy val policyStore = storeProvider.policyStore + protected lazy val userStateStore = storeProvider.userStateStore + protected lazy val resourceEventStore = storeProvider.resourceEventStore def findUserStateAtEndOfBillingMonth(userId: String, billingMonthInfo: BillingMonthInfo, - storeProvider: StoreProvider, currentUserState: UserState, defaultResourcesMap: DSLResourcesMap, - accounting: Accounting, - algorithmCompiler: CostPolicyAlgorithmCompiler, calculationReason: UserStateChangeReason, clogOpt: Option[ContextualLogger] = None): UserState = { @@ -74,18 +77,12 @@ class UserStateComputations extends Loggable { doFullMonthlyBilling( userId, billingMonthInfo, - storeProvider, currentUserState, defaultResourcesMap, - accounting, - algorithmCompiler, calculationReason, Some(clog)) } - val userStateStore = storeProvider.userStateStore - val resourceEventStore = storeProvider.resourceEventStore - val userCreationMillis = currentUserState.userCreationMillis val userCreationDateCalc = new MutableDateCalc(userCreationMillis) val billingMonthStartMillis = billingMonthInfo.startMillis @@ -162,11 +159,9 @@ class UserStateComputations extends Loggable { def processResourceEvent(startingUserState: UserState, userStateWorker: UserStateWorker, currentResourceEvent: ResourceEventModel, - policyStore: PolicyStore, stateChangeReason: UserStateChangeReason, billingMonthInfo: BillingMonthInfo, walletEntriesBuffer: mutable.Buffer[NewWalletEntry], - algorithmCompiler: CostPolicyAlgorithmCompiler, clogOpt: Option[ContextualLogger] = None): UserState = { val clog = ContextualLogger.fromOther(clogOpt, logger, "walletEntriesForResourceEvent(%s)", currentResourceEvent.id) @@ -177,7 +172,6 @@ class UserStateComputations extends Loggable { val theInstanceId = currentResourceEvent.safeInstanceId val theValue = currentResourceEvent.value - val accounting = userStateWorker.accounting val resourcesMap = userStateWorker.resourcesMap val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent) @@ -214,7 +208,7 @@ class UserStateComputations extends Loggable { } else { val defaultInitialAmount = costPolicy.getResourceInstanceInitialAmount val oldAmount = _workingUserState.getResourceInstanceAmount(theResource, theInstanceId, defaultInitialAmount) - val oldCredits = _workingUserState.creditsSnapshot.creditAmount + val oldCredits = _workingUserState.totalCredits // A. Compute new resource instance accumulating amount val newAmount = costPolicy.computeNewAccumulatingAmount(oldAmount, theValue) @@ -222,8 +216,8 @@ class UserStateComputations extends Loggable { clog.debug("theValue = %s, oldAmount = %s, newAmount = %s, oldCredits = %s", theValue, oldAmount, newAmount, oldCredits) // B. Compute new wallet entries - clog.debug("agreementsSnapshot = %s", _workingUserState.agreementsSnapshot) - val alltimeAgreements = _workingUserState.agreementsSnapshot.agreementsByTimeslot + clog.debug("agreementsSnapshot = %s", _workingUserState.agreementHistory) + val alltimeAgreements = _workingUserState.agreementHistory.agreementNamesByTimeslot // clog.debug("Computing full chargeslots") val (referenceTimeslot, fullChargeslots) = accounting.computeFullChargeslots( @@ -277,9 +271,8 @@ class UserStateComputations extends Loggable { } _workingUserState = _workingUserState.copy( - creditsSnapshot = CreditSnapshot(newCredits), - stateChangeCounter = _workingUserState.stateChangeCounter + 1, - totalEventsProcessedCounter = _workingUserState.totalEventsProcessedCounter + 1 + totalCredits = newCredits, + stateChangeCounter = _workingUserState.stateChangeCounter + 1 ) } } @@ -305,11 +298,9 @@ class UserStateComputations extends Loggable { def processResourceEvents(resourceEvents: Traversable[ResourceEventModel], startingUserState: UserState, userStateWorker: UserStateWorker, - policyStore: PolicyStore, stateChangeReason: UserStateChangeReason, billingMonthInfo: BillingMonthInfo, walletEntriesBuffer: mutable.Buffer[NewWalletEntry], - algorithmCompiler: CostPolicyAlgorithmCompiler, clogOpt: Option[ContextualLogger] = None): UserState = { var _workingUserState = startingUserState @@ -320,11 +311,9 @@ class UserStateComputations extends Loggable { _workingUserState, userStateWorker, currentResourceEvent, - policyStore, stateChangeReason, billingMonthInfo, walletEntriesBuffer, - algorithmCompiler, clogOpt ) } @@ -335,11 +324,8 @@ class UserStateComputations extends Loggable { def doFullMonthlyBilling(userId: String, billingMonthInfo: BillingMonthInfo, - storeProvider: StoreProvider, currentUserState: UserState, defaultResourcesMap: DSLResourcesMap, - accounting: Accounting, - algorithmCompiler: CostPolicyAlgorithmCompiler, calculationReason: UserStateChangeReason = NoSpecificChangeReason, clogOpt: Option[ContextualLogger] = None): UserState = { @@ -355,20 +341,14 @@ class UserStateComputations extends Loggable { val previousBillingMonthUserState = findUserStateAtEndOfBillingMonth( userId, billingMonthInfo.previousMonth, - storeProvider, currentUserState, defaultResourcesMap, - accounting, - algorithmCompiler, calculationReason.forPreviousBillingMonth, clogSome ) val startingUserState = previousBillingMonthUserState - val userStateStore = storeProvider.userStateStore - val resourceEventStore = storeProvider.resourceEventStore - val policyStore = storeProvider.policyStore val billingMonthStartMillis = billingMonthInfo.startMillis val billingMonthEndMillis = billingMonthInfo.stopMillis @@ -378,7 +358,7 @@ class UserStateComputations extends Loggable { // NOTE: The calculation reason is not the one we get from the previous user state but the one our caller specifies var _workingUserState = startingUserState.copyForChangeReason(calculationReason) - val userStateWorker = UserStateWorker.fromUserState(_workingUserState, accounting, defaultResourcesMap) + val userStateWorker = UserStateWorker.fromUserState(_workingUserState, defaultResourcesMap) userStateWorker.debugTheMaps(clog)(rcDebugInfo) @@ -394,11 +374,9 @@ class UserStateComputations extends Loggable { allResourceEventsForMonth, _workingUserState, userStateWorker, - policyStore, calculationReason, billingMonthInfo, newWalletEntries, - algorithmCompiler, clogSome ) @@ -420,7 +398,6 @@ class UserStateComputations extends Loggable { LatestResourceEventsWorker.fromList(specialEvents), ImplicitlyIssuedResourceEventsWorker.Empty, IgnoredFirstResourceEventsWorker.Empty, - userStateWorker.accounting, userStateWorker.resourcesMap ) @@ -428,11 +405,9 @@ class UserStateComputations extends Loggable { theirImplicitEnds, _workingUserState, specialUserStateWorker, - policyStore, calculationReason, billingMonthInfo, newWalletEntries, - algorithmCompiler, clogSome ) diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala index 6c0fb30..5e5ecd1 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala @@ -62,7 +62,6 @@ case class UserStateWorker(userID: String, previousResourceEvents: LatestResourceEventsWorker, implicitlyIssuedStartEvents: ImplicitlyIssuedResourceEventsWorker, ignoredFirstResourceEvents: IgnoredFirstResourceEventsWorker, - accounting: Accounting, resourcesMap: DSLResourcesMap) { /** @@ -175,13 +174,12 @@ case class UserStateWorker(userID: String, } object UserStateWorker { - def fromUserState(userState: UserState, accounting: Accounting, resourcesMap: DSLResourcesMap): UserStateWorker = { + def fromUserState(userState: UserState, 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/AgreementHistory.scala b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementHistory.scala index 18e1dd5..8a8177f 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/data/AgreementHistory.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/data/AgreementHistory.scala @@ -60,8 +60,12 @@ case class AgreementHistory(agreements: List[AgreementHistoryItem]) { case Nil => () } - def agreementsByTimeslot: SortedMap[Timeslot, String] = { - TreeMap(agreements.map(ag => (ag.timeslot, ag.name)): _*) + def agreementNamesByTimeslot: SortedMap[Timeslot, String] = { + TreeMap(agreements.map(ag ⇒ (ag.timeslot, ag.name)): _*) + } + + def agreementsByTimeslot: SortedMap[Timeslot, AgreementHistoryItem] = { + TreeMap(agreements.map(ag ⇒ (ag.timeslot, ag)): _*) } /** @@ -74,6 +78,34 @@ case class AgreementHistory(agreements: List[AgreementHistoryItem]) { case None => None } } + + /** + * Returns the first, chronologically, agreement. + */ + def firstAgreement: Option[AgreementHistoryItem] = { + agreementsByTimeslot.valuesIterator.toList.lastOption + } + + /** + * Returns the name of the first, chronologically, agreement. + */ + def firstAgreementName: Option[String] = { + agreementNamesByTimeslot.valuesIterator.toList.lastOption + } + + /** + * Returns the last, chronologically, agreement. + */ + def lastAgreement: Option[AgreementHistoryItem] = { + agreementsByTimeslot.valuesIterator.toList.headOption + } + + /** + * Returns the name of the last, chronologically, agreement. + */ + def lastAgreementName: Option[String] = { + agreementNamesByTimeslot.valuesIterator.toList.headOption + } } object AgreementHistory { diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala index 7921053..bb37cec 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/data/ImplicitlyIssuedResourceEventsSnapshot.scala @@ -59,3 +59,7 @@ case class ImplicitlyIssuedResourceEventsSnapshot(implicitlyIssuedEvents: List[R } } +object ImplicitlyIssuedResourceEventsSnapshot { + final val Empty = ImplicitlyIssuedResourceEventsSnapshot(Nil) +} + diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala index 8c66c8c..271ca43 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala @@ -60,3 +60,6 @@ case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel] } } +object LatestResourceEventsSnapshot { + final val Empty = LatestResourceEventsSnapshot(Nil) +} diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala index fd7649c..f11a3e0 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/data/OwnedResourcesSnapshot.scala @@ -87,4 +87,8 @@ case class OwnedResourcesSnapshot(resourceInstanceSnapshots: List[ResourceInstan (newOwnedResources, oldResourceInstanceOpt, newResourceInstance) } +} + +object OwnedResourcesSnapshot { + final val Empty = OwnedResourcesSnapshot(Nil) } \ No newline at end of file diff --git a/src/main/scala/gr/grnet/aquarium/computation/data/RoleHistory.scala b/src/main/scala/gr/grnet/aquarium/computation/data/RoleHistory.scala index fb97aab..1b09bee 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/data/RoleHistory.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/data/RoleHistory.scala @@ -50,8 +50,13 @@ case class RoleHistory( * The head role is the most recent. The same rule applies for the tail. */ roles: List[RoleHistoryItem]) { - def rolesByTimeslot: SortedMap[Timeslot, String] = { - TreeMap(roles.map(role => (role.timeslot, role.name)): _*) + + def roleNamesByTimeslot: SortedMap[Timeslot, String] = { + TreeMap(roles.map(role ⇒ (role.timeslot, role.name)): _*) + } + + def rolesByTimeslot: SortedMap[Timeslot, RoleHistoryItem] = { + TreeMap(roles.map(role ⇒ (role.timeslot, role)): _*) } def copyWithRole(role: String, validFrom: Long) = { @@ -95,6 +100,34 @@ case class RoleHistory( RoleHistory(newItems) } + + /** + * Returns the first, chronologically, role. + */ + def firstRole: Option[RoleHistoryItem] = { + rolesByTimeslot.valuesIterator.toList.lastOption + } + + /** + * Returns the name of the first, chronologically, role. + */ + def firstRoleName: Option[String] = { + roleNamesByTimeslot.valuesIterator.toList.lastOption + } + + /** + * Returns the last, chronologically, role. + */ + def lastRole: Option[RoleHistoryItem] = { + rolesByTimeslot.valuesIterator.toList.headOption + } + + /** + * Returns the name of the last, chronologically, role. + */ + def lastRoleName: Option[String] = { + roleNamesByTimeslot.valuesIterator.toList.headOption + } } object RoleHistory { 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 38af280..4b53aff 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala @@ -35,51 +35,25 @@ package gr.grnet.aquarium.logic.accounting -import gr.grnet.aquarium.util.shortClassNameOf import algorithm.CostPolicyAlgorithmCompiler import dsl._ import collection.immutable.SortedMap -import java.util.Date -import com.ckkloverdos.maybe.{NoVal, Maybe, Failed, Just} -import gr.grnet.aquarium.util.{ContextualLogger, CryptoUtils, Loggable} +import com.ckkloverdos.maybe.{NoVal, Maybe, Just} +import gr.grnet.aquarium.util.{ContextualLogger, Loggable} import gr.grnet.aquarium.store.PolicyStore -import gr.grnet.aquarium.event.model.WalletEntry -import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc} +import gr.grnet.aquarium.util.date.MutableDateCalc import gr.grnet.aquarium.{AquariumInternalError, AquariumException} import gr.grnet.aquarium.event.model.resource.ResourceEventModel /** - * A timeslot together with the algorithm and unit price that apply for this particular timeslot. - * - * @param startMillis - * @param stopMillis - * @param algorithmDefinition - * @param unitPrice - * @param computedCredits The computed credits - */ -case class Chargeslot(startMillis: Long, - stopMillis: Long, - algorithmDefinition: String, - unitPrice: Double, - computedCredits: Option[Double] = None) { - - override def toString = "%s(%s, %s, %s, %s, %s)".format( - shortClassNameOf(this), - new MutableDateCalc(startMillis).toYYYYMMDDHHMMSSSSS, - new MutableDateCalc(stopMillis).toYYYYMMDDHHMMSSSSS, - unitPrice, - computedCredits, - algorithmDefinition - ) -} - -/** * Methods for converting accounting events to wallet entries. * * @author Georgios Gousios * @author Christos KK Loverdos */ -trait Accounting extends DSLUtils with Loggable { +trait Accounting extends Loggable { + // TODO: favour composition over inheritance until we decide what to do with DSLUtils (and Accounting). + protected val dslUtils = new DSLUtils {} /** * Breaks a reference timeslot (e.g. billing period) according to policies and agreements. * @@ -125,8 +99,8 @@ trait Accounting extends DSLUtils with Loggable { val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveAlgorithmsAndPriceLists()") // Note that most of the code is taken from calcChangeChunks() - val alg = resolveEffectiveAlgorithmsForTimeslot(alignedTimeslot, agreement) - val pri = resolveEffectivePricelistsForTimeslot(alignedTimeslot, agreement) + val alg = dslUtils.resolveEffectiveAlgorithmsForTimeslot(alignedTimeslot, agreement) + val pri = dslUtils.resolveEffectivePricelistsForTimeslot(alignedTimeslot, agreement) val chargeChunks = splitChargeChunks(alg, pri) val algorithmByTimeslot = chargeChunks._1 val pricelistByTimeslot = chargeChunks._2 @@ -352,7 +326,7 @@ trait Accounting extends DSLUtils with Loggable { // This is it val credits = execAlgorithm.apply(valueMap) - chargeslot.copy(computedCredits = Some(credits)) + chargeslot.copyWithCredits(credits) } val result = referenceTimeslot -> fullChargeslots @@ -361,249 +335,6 @@ trait Accounting extends DSLUtils with Loggable { } /** - * Create a list of wallet entries by charging for a resource event. - * - * @param currentResourceEvent The resource event to create charges for - * @param agreements The user's agreement names, indexed by their - * applicability timeslot - * @param previousAmount The current state of the resource - * @param previousOccurred The last time the resource state was updated - */ - def chargeEvent(currentResourceEvent: ResourceEventModel, - agreements: SortedMap[Timeslot, String], - previousAmount: Double, - previousOccurred: Date, - related: List[WalletEntry]): Maybe[List[WalletEntry]] = { - - assert(previousOccurred.getTime <= currentResourceEvent.occurredMillis) - val occuredDate = new Date(currentResourceEvent.occurredMillis) - - /* The following makes sure that agreements exist between the start - * and end days of the processed event. As agreement updates are - * guaranteed not to leave gaps, this means that the event can be - * processed correctly, as at least one agreement will be valid - * throughout the event's life. - */ - assert( - agreements.keysIterator.exists { - p => p.includes(occuredDate) - } && agreements.keysIterator.exists { - p => p.includes(previousOccurred) - } - ) - - val t = Timeslot(previousOccurred, occuredDate) - - // Align policy and agreement validity timeslots to the event's boundaries - val policyTimeslots = t.align( - Policy.policies(previousOccurred, occuredDate).keysIterator.toList) - val agreementTimeslots = t.align(agreements.keysIterator.toList) - - /* - * Get a set of timeslot slices covering the different durations of - * agreements and policies. - */ - val aligned = alignTimeslots(policyTimeslots, agreementTimeslots) - - val walletEntries = aligned.map { - x => - // Retrieve agreement from the policy valid at time of event - val agreementName = agreements.find(y => y._1.contains(x)) match { - case Some(x) => x - case None => return Failed(new AccountingException(("Cannot find" + - " user agreement for period %s").format(x))) - } - - // Do the wallet entry calculation - val entries = chargeEvent( - currentResourceEvent, - Policy.policy(x.from).findAgreement(agreementName._2).getOrElse( - return Failed(new AccountingException("Cannot get agreement for %s".format())) - ), - previousAmount, - previousOccurred, - related, - Some(x) - ) match { - case Just(x) => x - case Failed(f) => return Failed(f) - case NoVal => List() - } - entries - }.flatten - - Just(walletEntries) - } - - /** - * Creates a list of wallet entries by applying the agreement provisions on - * the resource state. - * - * @param event The resource event to create charges for - * @param agr The agreement implementation to use - * @param previousAmount The current state of the resource - * @param previousOccurred The timestamp of the previous event - * @param related Related wallet entries (TODO: should remove) - * @param chargeFor The duration for which the charge should be done. - * Should fall between the previous and current - * resource event boundaries - * @return A list of wallet entries, one for each - */ - def chargeEvent(event: ResourceEventModel, - agr: DSLAgreement, - previousAmount: Double, - previousOccurred: Date, - related: List[WalletEntry], - chargeFor: Option[Timeslot]): Maybe[List[WalletEntry]] = { - - // If chargeFor is not null, make sure it falls within - // event time boundaries - chargeFor.map{x => assert(true, - Timeslot(previousOccurred, new Date(event.occurredMillis)))} - -// if (!event.validate()) -// return Failed(new AccountingException("Event not valid")) - - val policy = Policy.policy - val dslResource = policy.findResource(event.resource) match { - case Some(x) => x - case None => return Failed( - new AccountingException("No resource [%s]".format(event.resource))) - } - - /* This is a safeguard against the special case where the last - * resource state update, as marked by the lastUpdate parameter - * is equal to the time of the event occurrence. This means that - * this is the first time the resource state has been recorded. - * Charging in this case only makes sense for discrete resources. - */ - if (previousOccurred.getTime == event.occurredMillis) { - dslResource.costPolicy match { - case DiscreteCostPolicy => //Ok - case _ => return Just(List()) - } - } - - val creditCalculationValueM = dslResource.costPolicy.getValueForCreditCalculation(Just(previousAmount), event.value) - val amount = creditCalculationValueM match { - case failed @ Failed(_) ⇒ - return failed - case Just(amount) ⇒ - amount - case NoVal ⇒ - 0.0 - } - - // We don't do strict checking for all cases for OnOffPolicies as - // above, since this point won't be reached in case of error. - val isFinal = dslResource.costPolicy match { - case OnOffCostPolicy => - OnOffPolicyResourceState(previousAmount) match { - case OnResourceState => false - case OffResourceState => true - } - case _ => true - } - - /* - * Get the timeslot for which this event will be charged. In case we - * have a discrete resource, we do not really care for the time duration - * of an event. To process all events in a uniform way, we create an - * artificial timeslot lasting the minimum amount of time. In all other - * cases, we first check whether a desired charge period passed as - * an argument. - */ - val timeslot = dslResource.costPolicy match { - case DiscreteCostPolicy => Timeslot(new Date(event.occurredMillis - 1), - new Date(event.occurredMillis)) - case _ => chargeFor match { - case Some(x) => x - case None => Timeslot(previousOccurred, new Date(event.occurredMillis)) - } - } - - /* - * The following splits the chargable timeslot into smaller timeslots to - * comply with different applicability periods for algorithms and - * pricelists defined by the provided agreement. - */ - val chargeChunks = calcChangeChunks(agr, amount, dslResource, timeslot) - - val timeReceived = TimeHelpers.nowMillis() - - val rel = event.id :: related.map{x => x.sourceEventIDs}.flatten - - val entries = chargeChunks.map { c=> - WalletEntry( - id = CryptoUtils.sha1(c.id), - occurredMillis = event.occurredMillis, - receivedMillis = timeReceived, - sourceEventIDs = rel, - value = c.cost, - reason = c.reason, - userId = event.userID, - resource = event.resource, - instanceId = event.instanceID, - finalized = isFinal - ) - } - Just(entries) - } - - /** - * Create a - */ - def calcChangeChunks(agr: DSLAgreement, volume: Double, - res: DSLResource, t: Timeslot): List[ChargeChunk] = { - - val alg = resolveEffectiveAlgorithmsForTimeslot(t, agr) - val pri = resolveEffectivePricelistsForTimeslot(t, agr) - val chunks = splitChargeChunks(alg, pri) - val algChunked = chunks._1 - val priChunked = chunks._2 - - assert(algChunked.size == priChunked.size) - - res.costPolicy match { - case DiscreteCostPolicy => calcChargeChunksDiscrete(algChunked, priChunked, volume, res) - case _ => calcChargeChunksContinuous(algChunked, priChunked, volume, res) - } - } - - /** - * Get a list of charge chunks for discrete resources. - */ - private[logic] - def calcChargeChunksDiscrete(algChunked: Map[Timeslot, DSLAlgorithm], - priChunked: Map[Timeslot, DSLPriceList], - volume: Double, res: DSLResource): List[ChargeChunk] = { - // In case of descrete resources, we only a expect a - assert(algChunked.size == 1) - assert(priChunked.size == 1) - assert(algChunked.keySet.head.compare(priChunked.keySet.head) == 0) - - List(ChargeChunk(volume, - algChunked.valuesIterator.next.algorithms.getOrElse(res, ""), - priChunked.valuesIterator.next.prices.getOrElse(res, 0), - algChunked.keySet.head, res)) - } - - /** - * Get a list of charge chunks for continuous resources. - */ - private[logic] - def calcChargeChunksContinuous(algChunked: Map[Timeslot, DSLAlgorithm], - priChunked: Map[Timeslot, DSLPriceList], - volume: Double, res: DSLResource): List[ChargeChunk] = { - algChunked.keysIterator.map { - x => - ChargeChunk(volume, - algChunked.get(x).get.algorithms.getOrElse(res, ""), - priChunked.get(x).get.prices.getOrElse(res, 0), x, res) - }.toList - } - - /** * Align charge timeslots between algorithms and pricelists. As algorithm * and pricelists can have different effectivity periods, this method * examines them and splits them as necessary. @@ -678,44 +409,3 @@ trait Accounting extends DSLUtils with Loggable { } } } - -/** - * Encapsulates a computation for a specific timeslot of - * resource usage. - */ -case class ChargeChunk(value: Double, algorithm: String, - price: Double, when: Timeslot, - resource: DSLResource) { - assert(value > 0) - assert(!algorithm.isEmpty) - assert(resource != null) - - def cost(): Double = - //TODO: Apply the algorithm, when we start parsing it - resource.costPolicy match { - case DiscreteCostPolicy => - value * price - case _ => - value * price * when.hours - } - - def reason(): String = - resource.costPolicy match { - case DiscreteCostPolicy => - "%f %s at %s @ %f/%s".format(value, resource.unit, when.from, price, - resource.unit) - case ContinuousCostPolicy => - "%f %s of %s from %s to %s @ %f/%s".format(value, resource.unit, - resource.name, when.from, when.to, price, resource.unit) - case OnOffCostPolicy => - "%f %s of %s from %s to %s @ %f/%s".format(when.hours, resource.unit, - resource.name, when.from, when.to, price, resource.unit) - } - - def id(): String = - CryptoUtils.sha1("%f%s%f%s%s%d".format(value, algorithm, price, when.toString, - resource.name, TimeHelpers.nowMillis())) -} - -/** An exception raised when something goes wrong with accounting */ -class AccountingException(msg: String) extends AquariumException(msg) diff --git a/src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/Chargeslot.scala similarity index 68% rename from src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala rename to src/main/scala/gr/grnet/aquarium/logic/accounting/Chargeslot.scala index 8d3284f..c792505 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/DefaultUserStateComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/Chargeslot.scala @@ -33,11 +33,34 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.computation +package gr.grnet.aquarium.logic.accounting + +import gr.grnet.aquarium.util._ +import gr.grnet.aquarium.util.date.MutableDateCalc /** - * Default implementation for [[gr.grnet.aquarium.computation.UserStateComputations]]. + * Represents a timeslot together with the algorithm and unit price that apply for this particular timeslot. * * @author Christos KK Loverdos */ -object DefaultUserStateComputations extends UserStateComputations \ No newline at end of file + +case class Chargeslot( + startMillis: Long, + stopMillis: Long, + algorithmDefinition: String, + unitPrice: Double, + computedCredits: Option[Double] = None) { + + def copyWithCredits(credits: Double) = { + copy(computedCredits = Some(credits)) + } + + override def toString = "%s(%s, %s, %s, %s, %s)".format( + shortClassNameOf(this), + new MutableDateCalc(startMillis).toYYYYMMDDHHMMSSSSS, + new MutableDateCalc(stopMillis).toYYYYMMDDHHMMSSSSS, + unitPrice, + computedCredits, + algorithmDefinition + ) +} diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/Policy.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/Policy.scala index 94f009c..e1c5143 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/Policy.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/Policy.scala @@ -39,13 +39,13 @@ import dsl.{Timeslot, DSLPolicy, DSL} import gr.grnet.aquarium.Aquarium._ import java.io.{InputStream, FileInputStream, File} import java.util.Date -import gr.grnet.aquarium.util.date.TimeHelpers import gr.grnet.aquarium.util.Loggable import java.util.concurrent.atomic.AtomicReference -import gr.grnet.aquarium.Aquarium import gr.grnet.aquarium.Aquarium.Keys import com.ckkloverdos.maybe.{Failed, NoVal, Just} import collection.immutable.{TreeMap, SortedMap} +import gr.grnet.aquarium.{AquariumException, Aquarium} +import gr.grnet.aquarium.util.date.{MutableDateCalc, TimeHelpers} /** * Searches for and loads the applicable accounting policy @@ -80,7 +80,7 @@ object Policy extends DSL with Loggable { } match { case Some(x) => x._2 case None => - throw new AccountingException("No valid policy for date: %s".format(at)) + throw new AquariumException("No valid policy for date: %s".format(new MutableDateCalc(at))) } } diff --git a/src/main/scala/gr/grnet/aquarium/store/StoreProvider.scala b/src/main/scala/gr/grnet/aquarium/store/StoreProvider.scala index 9b42d01..ca141d7 100644 --- a/src/main/scala/gr/grnet/aquarium/store/StoreProvider.scala +++ b/src/main/scala/gr/grnet/aquarium/store/StoreProvider.scala @@ -43,7 +43,6 @@ package gr.grnet.aquarium.store trait StoreProvider { def userStateStore: UserStateStore def resourceEventStore: ResourceEventStore - def walletEntryStore: WalletEntryStore def imEventStore: IMEventStore def policyStore: PolicyStore } diff --git a/src/main/scala/gr/grnet/aquarium/store/WalletEntryStore.scala b/src/main/scala/gr/grnet/aquarium/store/WalletEntryStore.scala deleted file mode 100644 index 0053cc7..0000000 --- a/src/main/scala/gr/grnet/aquarium/store/WalletEntryStore.scala +++ /dev/null @@ -1,71 +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.store - -import java.util.Date -import com.ckkloverdos.maybe.Maybe -import gr.grnet.aquarium.event.model.WalletEntry - -/** - * A store for Wallet entries. - * - * @author Georgios Gousios - * @author Christos KK Loverdos - */ -trait WalletEntryStore { - - def storeWalletEntry(entry: WalletEntry): Maybe[RecordID] - - def findWalletEntryById(id: String): Maybe[WalletEntry] - - def findUserWalletEntries(userId: String): List[WalletEntry] - - def findUserWalletEntriesFromTo(userId: String, from: Date, to: Date): List[WalletEntry] - - /** - * Finds latest wallet entries with same timestamp. - */ - def findLatestUserWalletEntries(userId: String): Maybe[List[WalletEntry]] - - /** - * Find the previous entry in the user's wallet for the provided resource - * instance id. - */ - def findPreviousEntry(userId: String, resource: String, - instanceId: String, finalized: Option[Boolean]): List[WalletEntry] - - def findWalletEntriesAfter(userId: String, from: Date): List[WalletEntry] -} \ No newline at end of file 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 c23605a..a269793 100644 --- a/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala @@ -36,19 +36,19 @@ package gr.grnet.aquarium.store.memory import com.ckkloverdos.props.Props -import com.ckkloverdos.maybe.{NoVal, Just, Maybe} +import com.ckkloverdos.maybe.{NoVal, Just} import gr.grnet.aquarium.store._ import scala.collection.JavaConversions._ -import java.util.Date import collection.mutable.ConcurrentMap import java.util.concurrent.ConcurrentHashMap import gr.grnet.aquarium.uid.ConcurrentVMLocalUIDGenerator import gr.grnet.aquarium.Configurable -import gr.grnet.aquarium.event.model.{WalletEntry, PolicyEntry} +import gr.grnet.aquarium.event.model.PolicyEntry import gr.grnet.aquarium.event.model.im.{StdIMEvent, IMEventModel} import org.bson.types.ObjectId import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel} import gr.grnet.aquarium.computation.UserState +import gr.grnet.aquarium.util.Tags /** * An implementation of various stores that persists data in memory. @@ -62,19 +62,15 @@ import gr.grnet.aquarium.computation.UserState class MemStore extends UserStateStore with Configurable with PolicyStore with ResourceEventStore with IMEventStore - with WalletEntryStore with StoreProvider { override type IMEvent = MemIMEvent override type ResourceEvent = MemResourceEvent - private[this] val idGen = new ConcurrentVMLocalUIDGenerator(1000) - private[this] var _userStates = List[UserState]() private[this] var _policyEntries = List[PolicyEntry]() private[this] var _resourceEvents = List[ResourceEvent]() - private[this] val walletEntriesById: ConcurrentMap[String, WalletEntry] = new ConcurrentHashMap[String, WalletEntry]() private[this] val imEventById: ConcurrentMap[String, MemIMEvent] = new ConcurrentHashMap[String, MemIMEvent]() @@ -85,11 +81,10 @@ class MemStore extends UserStateStore override def toString = { val map = Map( - "UserState" -> _userStates.size, - "ResourceEvent" -> _resourceEvents.size, - "IMEvent" -> imEventById.size, - "PolicyEntry" -> _policyEntries.size, - "WalletEntry" -> walletEntriesById.size + Tags.UserStateTag -> _userStates.size, + Tags.ResourceEventTag -> _resourceEvents.size, + Tags.IMEventTag -> imEventById.size, + "PolicyEntry" -> _policyEntries.size ) "MemStore(%s)" format map @@ -100,8 +95,6 @@ class MemStore extends UserStateStore def resourceEventStore = this - def walletEntryStore = this - def imEventStore = this def policyStore = this @@ -110,7 +103,7 @@ class MemStore extends UserStateStore //+ UserStateStore def insertUserState(userState: UserState): UserState = { - _userStates = userState.copy(_id = new ObjectId()) :: _userStates + _userStates = userState.copy(_id = new ObjectId().toString) :: _userStates userState } @@ -162,48 +155,6 @@ class MemStore extends UserStateStore } //- UserStateStore - //- WalletEntryStore - def storeWalletEntry(entry: WalletEntry): Maybe[RecordID] = { - walletEntriesById.put(entry.id, entry) - Just(RecordID(entry.id)) - } - - def findWalletEntryById(id: String): Maybe[WalletEntry] = { - Maybe(walletEntriesById.apply(id)) - } - - def findUserWalletEntries(userId: String): List[WalletEntry] = { - walletEntriesById.valuesIterator.filter(_.userId == userId).toList - } - - def findUserWalletEntriesFromTo(userId: String, from: Date, to: Date): List[WalletEntry] = { - walletEntriesById.valuesIterator.filter { we ⇒ - val receivedDate = we.receivedDate - - we.userId == userId && - ( (from before receivedDate) || (from == receivedDate) ) && - ( (to after receivedDate) || (to == receivedDate) ) - true - }.toList - } - - def findLatestUserWalletEntries(userId: String): Maybe[List[WalletEntry]] = NoVal - - def findPreviousEntry(userId: String, - resource: String, - instanceId: String, - finalized: Option[Boolean]): List[WalletEntry] = Nil - - def findWalletEntriesAfter(userID: String, from: Date): List[WalletEntry] = { - walletEntriesById.valuesIterator.filter { we ⇒ - val occurredDate = we.occurredDate - - we.userId == userID && - ( (from before occurredDate) || (from == occurredDate) ) - }.toList - } - //- WalletEntryStore - //+ ResourceEventStore def createResourceEventFromOther(event: ResourceEventModel): ResourceEvent = { if(event.isInstanceOf[MemResourceEvent]) event.asInstanceOf[MemResourceEvent] 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 25981bd..c27fe2c 100644 --- a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala @@ -44,9 +44,7 @@ import gr.grnet.aquarium.event.model.im.IMEventModel.{Names ⇒ IMEventNames} import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.event.model.resource.ResourceEventModel.{Names ⇒ ResourceEventNames} import gr.grnet.aquarium.store._ -import gr.grnet.aquarium.event.model.WalletEntry.{JsonNames ⇒ WalletJsonNames} import gr.grnet.aquarium.event.model.PolicyEntry.{JsonNames ⇒ PolicyJsonNames} -import java.util.Date import gr.grnet.aquarium.logic.accounting.Policy import com.mongodb._ import org.bson.types.ObjectId @@ -54,7 +52,7 @@ import com.ckkloverdos.maybe.Maybe import gr.grnet.aquarium.util._ import gr.grnet.aquarium.converter.Conversions import gr.grnet.aquarium.computation.UserState -import gr.grnet.aquarium.event.model.{ExternalEventModel, WalletEntry, PolicyEntry} +import gr.grnet.aquarium.event.model.{ExternalEventModel, PolicyEntry} /** * Mongodb implementation of the various aquarium stores. @@ -69,7 +67,6 @@ class MongoDBStore( val password: String) extends ResourceEventStore with UserStateStore - with WalletEntryStore with IMEventStore with PolicyStore with Loggable { @@ -81,7 +78,6 @@ class MongoDBStore( private[store] lazy val userStates = getCollection(MongoDBStore.USER_STATES_COLLECTION) private[store] lazy val imEvents = getCollection(MongoDBStore.IM_EVENTS_COLLECTION) private[store] lazy val unparsedIMEvents = getCollection(MongoDBStore.UNPARSED_IM_EVENTS_COLLECTION) - private[store] lazy val walletEntries = getCollection(MongoDBStore.WALLET_ENTRIES_COLLECTION) private[store] lazy val policyEntries = getCollection(MongoDBStore.POLICY_ENTRIES_COLLECTION) private[this] def getCollection(name: String): DBCollection = { @@ -236,95 +232,6 @@ class MongoDBStore( } //- UserStateStore - //+WalletEntryStore - def storeWalletEntry(entry: WalletEntry): Maybe[RecordID] = { - Maybe { - MongoDBStore.storeAny[WalletEntry]( - entry, - walletEntries, - WalletJsonNames.id, - (e) => e.id, - MongoDBStore.jsonSupportToDBObject) - } - } - - def findWalletEntryById(id: String): Maybe[WalletEntry] = { - MongoDBStore.findBy(WalletJsonNames.id, id, walletEntries, MongoDBStore.dbObjectToWalletEntry): Maybe[WalletEntry] - } - - def findUserWalletEntries(userId: String) = { - // TODO: optimize - findUserWalletEntriesFromTo(userId, new Date(0), new Date(Int.MaxValue)) - } - - def findUserWalletEntriesFromTo(userId: String, from: Date, to: Date) : List[WalletEntry] = { - val q = new BasicDBObject() - // TODO: Is this the correct way for an AND query? - q.put(WalletJsonNames.occurredMillis, new BasicDBObject("$gt", from.getTime)) - q.put(WalletJsonNames.occurredMillis, new BasicDBObject("$lt", to.getTime)) - q.put(WalletJsonNames.userId, userId) - - MongoDBStore.runQuery[WalletEntry](q, walletEntries)(MongoDBStore.dbObjectToWalletEntry)(Some(_sortByTimestampAsc)) - } - - def findWalletEntriesAfter(userId: String, from: Date) : List[WalletEntry] = { - val q = new BasicDBObject() - q.put(WalletJsonNames.occurredMillis, new BasicDBObject("$gt", from.getTime)) - q.put(WalletJsonNames.userId, userId) - - MongoDBStore.runQuery[WalletEntry](q, walletEntries)(MongoDBStore.dbObjectToWalletEntry)(Some(_sortByTimestampAsc)) - } - - def findLatestUserWalletEntries(userId: String) = { - Maybe { - val orderBy = new BasicDBObject(WalletJsonNames.occurredMillis, -1) // -1 is descending order - val cursor = walletEntries.find().sort(orderBy) - - try { - val buffer = new scala.collection.mutable.ListBuffer[WalletEntry] - if(cursor.hasNext) { - val walletEntry = MongoDBStore.dbObjectToWalletEntry(cursor.next()) - buffer += walletEntry - - var _previousOccurredMillis = walletEntry.occurredMillis - var _ok = true - - while(cursor.hasNext && _ok) { - val walletEntry = MongoDBStore.dbObjectToWalletEntry(cursor.next()) - var currentOccurredMillis = walletEntry.occurredMillis - _ok = currentOccurredMillis == _previousOccurredMillis - - if(_ok) { - buffer += walletEntry - } - } - - buffer.toList - } else { - null - } - } finally { - cursor.close() - } - } - } - - def findPreviousEntry(userId: String, resource: String, - instanceId: String, - finalized: Option[Boolean]): List[WalletEntry] = { - val q = new BasicDBObject() - q.put(WalletJsonNames.userId, userId) - q.put(WalletJsonNames.resource, resource) - q.put(WalletJsonNames.instanceId, instanceId) - finalized match { - case Some(x) => q.put(WalletJsonNames.finalized, x) - case None => - } - - MongoDBStore.runQuery[WalletEntry](q, walletEntries)(MongoDBStore.dbObjectToWalletEntry)(Some(_sortByTimestampAsc)) - } - //-WalletEntryStore - //+IMEventStore def createIMEventFromJson(json: String) = { MongoDBStore.createIMEventFromJson(json) @@ -463,13 +370,6 @@ object MongoDBStore { final val UNPARSED_IM_EVENTS_COLLECTION = "unparsed_imevents" /** - * Collection holding [[gr.grnet.aquarium.event.model.WalletEntry]]. - * - * Wallet entries are generated internally in Aquarium. - */ - final val WALLET_ENTRIES_COLLECTION = "wallets" - - /** * Collection holding [[gr.grnet.aquarium.logic.accounting.dsl.DSLPolicy]]. */ // final val POLICIES_COLLECTION = "policies" @@ -483,10 +383,6 @@ object MongoDBStore { UserState.fromJson(JSON.serialize(dbObj)) } - def dbObjectToWalletEntry(dbObj: DBObject): WalletEntry = { - WalletEntry.fromJson(JSON.serialize(dbObj)) - } - def dbObjectToPolicyEntry(dbObj: DBObject): PolicyEntry = { PolicyEntry.fromJson(JSON.serialize(dbObj)) } diff --git a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStoreProvider.scala b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStoreProvider.scala index d658eca..1efb062 100644 --- a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStoreProvider.scala +++ b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStoreProvider.scala @@ -81,7 +81,6 @@ class MongoDBStoreProvider extends StoreProvider with Configurable { def userStateStore = _mongoDBStore def resourceEventStore = _mongoDBStore - def walletEntryStore = _mongoDBStore def imEventStore = _mongoDBStore def policyStore = _mongoDBStore } diff --git a/src/main/scala/gr/grnet/aquarium/util/Tags.scala b/src/main/scala/gr/grnet/aquarium/util/Tags.scala index d888502..a529315 100644 --- a/src/main/scala/gr/grnet/aquarium/util/Tags.scala +++ b/src/main/scala/gr/grnet/aquarium/util/Tags.scala @@ -44,4 +44,5 @@ package gr.grnet.aquarium.util object Tags { final val ResourceEventTag = newTag("ResourceEvent") final val IMEventTag = newTag("IMEvent") + final val UserStateTag = newTag("UserState") } diff --git a/src/test/resources/aquarium.properties b/src/test/resources/aquarium.properties index 1be421d..dfccb83 100644 --- a/src/test/resources/aquarium.properties +++ b/src/test/resources/aquarium.properties @@ -95,9 +95,6 @@ user.state.store.class=gr.grnet.aquarium.store.memory.MemStore # Override the event store (if present, it will not be given by the store provider above) resource.event.store.class=gr.grnet.aquarium.store.memory.MemStore -# Override the WalletEntry store (if present, it will not be given by the store provider above) -wallet.entry.store.class=gr.grnet.aquarium.store.memory.MemStore - # Override the user event store (if present, it will not be given by the store provider above) user.event.store.class=gr.grnet.aquarium.store.memory.MemStore diff --git a/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala b/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala index 8847f04..83d27e0 100644 --- a/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala +++ b/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala @@ -36,14 +36,11 @@ package gr.grnet.aquarium.logic.test import gr.grnet.aquarium.util.TestMethods -import org.junit.{Test} -import gr.grnet.aquarium.logic.accounting.dsl.Timeslot +import org.junit.Test import java.util.Date import junit.framework.Assert._ -import gr.grnet.aquarium.logic.accounting.{Accounting} -import gr.grnet.aquarium.event.model.WalletEntry -import com.ckkloverdos.maybe.Just -import gr.grnet.aquarium.event.model.resource.StdResourceEvent +import gr.grnet.aquarium.logic.accounting.Accounting +import gr.grnet.aquarium.logic.accounting.dsl.{DSLUtils, Timeslot} /** * Tests for the methods that do accounting @@ -51,7 +48,6 @@ import gr.grnet.aquarium.event.model.resource.StdResourceEvent * @author Georgios Gousios */ class AccountingTest extends DSLTestBase with Accounting with TestMethods { - @Test def testAlignTimeslots() { var a = List(Timeslot(0,1)) @@ -82,8 +78,8 @@ class AccountingTest extends DSLTestBase with Accounting with TestMethods { val from = new Date(1322555880000L) //Tue, 29 Nov 2011 10:38:00 EET val to = new Date(1322689082000L) //Wed, 30 Nov 2011 23:38:02 EET val agr = dsl.findAgreement("complextimeslots").get - a = resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr).keySet.toList - b = resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr).keySet.toList + a = dslUtils.resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr).keySet.toList + b = dslUtils.resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr).keySet.toList result = alignTimeslots(a, b) assertEquals(9, result.size) @@ -98,8 +94,8 @@ class AccountingTest extends DSLTestBase with Accounting with TestMethods { val agr = dsl.findAgreement("scaledbandwidth").get - val alg = resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr) - val price = resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr) + val alg = dslUtils.resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr) + val price = dslUtils.resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr) val chunks = splitChargeChunks(alg, price) val algChunks = chunks._1 val priceChunks = chunks._2 @@ -113,52 +109,4 @@ class AccountingTest extends DSLTestBase with Accounting with TestMethods { t => assertEquals(t._1, t._2) } } - - @Test - def testChargeEvent(): Unit = { - before - val agr = dsl.findAgreement("scaledbandwidth").get - - //Simple, continuous resource - var evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "bandwidthup", "1", 123, "1", Map()) - var wallet = chargeEvent(evt, agr, 112, new Date(1325755902000L), List(), None) - wallet match { - case Just(x) => assertEquals(2, x.size) - case _ => fail("No results returned") - } - - //Complex resource event without details, should fail - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "vmtime", "1", 1, "1", Map()) - assertFailed[Exception, List[WalletEntry]](chargeEvent(evt, agr, 1, new Date(1325755902000L), List(), None)) - - //Complex, onoff resource - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "vmtime", "1", 1, "1", Map("vmid" -> "3")) - wallet = chargeEvent(evt, agr, 0, new Date(1325755902000L), List(), None) - wallet match { - case Just(x) => assertEquals(2, x.size) - case _ => fail("No results returned") - } - - //Complex, onoff resource, with wrong states, should fail - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "vmtime", "1", 1, "1", Map("vmid" -> "3")) - assertFailed[Exception, List[WalletEntry]](chargeEvent(evt, agr, 1, new Date(1325755902000L), List(), None)) - - //Simple, discrete resource - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "bookpages", "1", 120, "1", Map()) - wallet = chargeEvent(evt, agr, 15, new Date(1325755902000L), List(), None) - wallet match { - case Just(x) => assertEquals(1, x.size) - case _ => fail("No results returned") - } - - //Simple, discrete resource, time of last update equal to current event's occurred time - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "bookpages", "1", 120, "1", Map()) - wallet = chargeEvent(evt, agr, 15, new Date(1325762772000L), List(), None) - assertEquals(1, wallet.getOr(List(WalletEntry.zero, WalletEntry.zero)).size) - - //Simple, continuous resource, time of last update equal to current event's occurred time - evt = StdResourceEvent("123", 1325762772000L, 1325762774000L, "12", "1", "bandwidthup", "1", 123, "1", Map()) - wallet = chargeEvent(evt, agr, 15, new Date(1325762772000L), List(), None) - assertEquals(0, wallet.getOr(List(WalletEntry.zero)).size) - } } \ No newline at end of file diff --git a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala index 5dd3146..33b6eaf 100644 --- a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala +++ b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala @@ -128,9 +128,15 @@ aquariumpolicy: DiskspacePriceUnit ) - val Computations = new UserStateComputations + val aquarium = AquariumInstance.withStoreProviderClass(classOf[MemStore]) + Policy.withConfigurator(aquarium) + val StoreProvider = aquarium.storeProvider + val ResourceEventStore = StoreProvider.resourceEventStore - val DefaultPolicy = new DSL{} parse PolicyYAML + val Computations = aquarium.userStateComputations + + val DSL = new DSL {} + val DefaultPolicy = DSL parse PolicyYAML val DefaultAccounting = new Accounting{} val DefaultAlgorithm = new ExecutableCostPolicyAlgorithm { @@ -216,11 +222,6 @@ aquariumpolicy: val Synnefo = ClientSim("synnefo")(TheUIDGenerator) val Pithos = ClientSim("pithos" )(TheUIDGenerator) - val aquarium = AquariumInstance.withStoreProviderClass(classOf[MemStore]) - Policy.withConfigurator(aquarium) - val StoreProvider = aquarium.storeProvider - val ResourceEventStore = StoreProvider.resourceEventStore - val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1) val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate @@ -246,10 +247,9 @@ aquariumpolicy: val InitialUserState = UserState.createInitialUserState( userID = UserCKKL.userId, userCreationMillis = UserCreationDate.getTime, - isActive = true, - credits = 0.0, - roleNames = List("user"), - agreementName = DSLAgreement.DefaultAgreementName + totalCredits = 0.0, + initialRole = "default", + initialAgreement = DSLAgreement.DefaultAgreementName ) // By convention @@ -263,7 +263,7 @@ aquariumpolicy: def showUserState(clog: ContextualLogger, userState: UserState) { val id = userState._id val parentId = userState.parentUserStateId - val credits = userState.creditsSnapshot.creditAmount + val credits = userState.totalCredits val newWalletEntries = userState.newWalletEntries.map(_.toDebugString) val changeReason = userState.lastChangeReason val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString()) @@ -296,30 +296,19 @@ aquariumpolicy: Computations.doFullMonthlyBilling( UserCKKL.userId, billingMonthInfo, - StoreProvider, InitialUserState, DefaultResourcesMap, - DefaultAccounting, - DefaultCompiler, MonthlyBillingCalculation(billingMonthInfo), Some(clog) ) } - - private[this] - def justUserState(userStateM: Maybe[UserState]): UserState = { - userStateM match { - case Just(userState) ⇒ userState - case _ ⇒ throw new AquariumException("Unexpected %s".format(userStateM)) - } - } - + private[this] def expectCredits(clog: ContextualLogger, creditsConsumed: Double, userState: UserState, accuracy: Double = 0.001): Unit = { - val computed = userState.creditsSnapshot.creditAmount + val computed = userState.totalCredits Assert.assertEquals(-creditsConsumed, computed, accuracy) clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy) } -- 1.7.10.4