From 8157dc3b4ff494e86393e4374dfbce0428c05d42 Mon Sep 17 00:00:00 2001 From: Christos KK Loverdos Date: Wed, 22 Aug 2012 17:08:30 +0300 Subject: [PATCH] Implement Once behavior with the new scheme. Refactor in the process --- src/main/scala/gr/grnet/aquarium/Aquarium.scala | 8 +- .../gr/grnet/aquarium/AquariumAwareSkeleton.scala | 5 +- .../aquarium/actor/service/user/UserActor.scala | 45 ++- .../grnet/aquarium/charging/ChargingBehavior.scala | 90 ++--- .../charging/ChargingBehaviorSkeleton.scala | 349 +++++--------------- .../gr/grnet/aquarium/charging/ChargingInput.scala | 118 ------- .../grnet/aquarium/charging/ChargingService.scala | 51 ++- .../charging/ContinuousChargingBehavior.scala | 91 ++--- .../aquarium/charging/OnceChargingBehavior.scala | 92 ++++-- .../aquarium/charging/VMChargingBehavior.scala | 108 +++--- .../aquarium/charging/state/AgreementHistory.scala | 2 +- .../AgreementHistoryModel.scala} | 15 +- .../state/ResourceInstanceChargingState.scala | 70 ++++ .../ResourceInstanceChargingStateModel.scala} | 29 +- .../state/ResourcesChargingState.scala} | 27 +- .../aquarium/charging/state/StdUserState.scala | 9 +- .../aquarium/charging/state/UserStateModel.scala | 8 +- .../charging/state/UserStateModelSkeleton.scala | 36 +- .../charging/state/WorkingAgreementHistory.scala | 7 +- .../WorkingResourceInstanceChargingState.scala | 82 +++++ .../state/WorkingResourcesChargingState.scala | 67 ++++ .../aquarium/charging/state/WorkingUserState.scala | 221 +++++-------- .../computation/TimeslotComputations.scala | 7 +- .../connector/handler/IMEventPayloadHandler.scala | 3 +- .../gr/grnet/aquarium/policy/FullPriceTable.scala | 128 +++++-- .../aquarium/store/memory/MemStoreProvider.scala | 5 +- .../aquarium/store/mongodb/MongoDBUserState.scala | 13 +- 27 files changed, 826 insertions(+), 860 deletions(-) delete mode 100644 src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala rename src/main/scala/gr/grnet/aquarium/charging/{ChargingBehaviorAliases.scala => state/AgreementHistoryModel.scala} (81%) create mode 100644 src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala rename src/main/scala/gr/grnet/aquarium/{logic/accounting/algorithm/CostPolicyAlgorithmCompiler.scala => charging/state/ResourceInstanceChargingStateModel.scala} (74%) rename src/main/scala/gr/grnet/aquarium/{logic/accounting/algorithm/ExecutableChargingBehaviorAlgorithm.scala => charging/state/ResourcesChargingState.scala} (71%) create mode 100644 src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala create mode 100644 src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala diff --git a/src/main/scala/gr/grnet/aquarium/Aquarium.scala b/src/main/scala/gr/grnet/aquarium/Aquarium.scala index d078e76..fe606fd 100644 --- a/src/main/scala/gr/grnet/aquarium/Aquarium.scala +++ b/src/main/scala/gr/grnet/aquarium/Aquarium.scala @@ -294,21 +294,19 @@ final class Aquarium(env: Env) extends Lifecycle with Loggable { } def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = { + // A resource type never changes charging behavior. By definition. val className = resourceType.chargingBehavior _chargingBehaviorMap.get(className) match { case Some(chargingBehavior) ⇒ chargingBehavior case _ ⇒ - // It does not matter if this is entered by multiple threads and more than one instance of the same class - // is created. The returned instance is not meant to be cached. try { - val chargingBehavior = newInstance[ChargingBehavior](className) _chargingBehaviorMap synchronized { + val chargingBehavior = newInstance[ChargingBehavior](className) _chargingBehaviorMap = _chargingBehaviorMap.updated(className, chargingBehavior) + chargingBehavior } - - chargingBehavior } catch { case e: Exception ⇒ diff --git a/src/main/scala/gr/grnet/aquarium/AquariumAwareSkeleton.scala b/src/main/scala/gr/grnet/aquarium/AquariumAwareSkeleton.scala index 19978df..542e16a 100644 --- a/src/main/scala/gr/grnet/aquarium/AquariumAwareSkeleton.scala +++ b/src/main/scala/gr/grnet/aquarium/AquariumAwareSkeleton.scala @@ -38,6 +38,7 @@ package gr.grnet.aquarium import gr.grnet.aquarium.service.event.AquariumCreatedEvent import com.google.common.eventbus.Subscribe import gr.grnet.aquarium.util.Loggable +import gr.grnet.aquarium.util.LogHelpers.Debug /** * @@ -45,13 +46,13 @@ import gr.grnet.aquarium.util.Loggable */ trait AquariumAwareSkeleton extends AquariumAware { this: Loggable ⇒ - @volatile private var _aquarium: Aquarium = null + private var _aquarium: Aquarium = _ final protected def aquarium = _aquarium @Subscribe def awareOfAquarium(event: AquariumCreatedEvent) = { this._aquarium = event.aquarium - logger.debug("Aware of Aquarium: %s".format(this._aquarium)) + Debug(logger, "Aware of Aquarium: %s", this._aquarium) } } 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 476f3ce..7588811 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 @@ -37,38 +37,32 @@ package gr.grnet.aquarium.actor package service package user -import gr.grnet.aquarium.actor._ - -import gr.grnet.aquarium.actor.message.event.{ProcessResourceEvent, ProcessIMEvent} -import gr.grnet.aquarium.actor.message.config.{InitializeUserActorState, AquariumPropertiesLoaded} import gr.grnet.aquarium.util.date.TimeHelpers import gr.grnet.aquarium.service.event.BalanceEvent import gr.grnet.aquarium.event.model.im.IMEventModel -import message._ -import config.AquariumPropertiesLoaded -import config.InitializeUserActorState -import event.ProcessIMEvent -import event.ProcessResourceEvent +import gr.grnet.aquarium.actor.message.config.AquariumPropertiesLoaded +import gr.grnet.aquarium.actor.message.config.InitializeUserActorState +import gr.grnet.aquarium.actor.message.event.ProcessIMEvent +import gr.grnet.aquarium.actor.message.event.ProcessResourceEvent import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf} -import gr.grnet.aquarium.{Aquarium, AquariumInternalError} +import gr.grnet.aquarium.AquariumInternalError import gr.grnet.aquarium.computation.BillingMonthInfo -import gr.grnet.aquarium.charging.state.UserStateBootstrap -import gr.grnet.aquarium.charging.state.{WorkingAgreementHistory, WorkingUserState, UserStateModel} +import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel} import gr.grnet.aquarium.charging.reason.{InitialUserActorSetup, RealtimeChargingReason} -import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement} +import gr.grnet.aquarium.policy.PolicyDefinedFullPriceTableRef import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel} -import message.GetUserBalanceRequest -import message.GetUserBalanceResponse -import message.GetUserBalanceResponseData -import message.GetUserStateRequest -import message.GetUserStateResponse -import message.GetUserWalletRequest -import message.GetUserWalletResponse -import message.GetUserWalletResponseData -import scala.Left +import gr.grnet.aquarium.actor.message.GetUserBalanceRequest +import gr.grnet.aquarium.actor.message.GetUserBalanceResponse +import gr.grnet.aquarium.actor.message.GetUserBalanceResponseData +import gr.grnet.aquarium.actor.message.GetUserStateRequest +import gr.grnet.aquarium.actor.message.GetUserStateResponse +import gr.grnet.aquarium.actor.message.GetUserWalletRequest +import gr.grnet.aquarium.actor.message.GetUserWalletResponse +import gr.grnet.aquarium.actor.message.GetUserWalletResponseData +import gr.grnet.aquarium.actor.message.GetUserBillRequest +import gr.grnet.aquarium.actor.message.GetUserBillResponse +import gr.grnet.aquarium.actor.message.GetUserBillResponseData import gr.grnet.aquarium.charging.state.WorkingAgreementHistory -import scala.Some -import scala.Right import gr.grnet.aquarium.policy.StdUserAgreement import gr.grnet.aquarium.charging.state.UserStateBootstrap import gr.grnet.aquarium.charging.bill.BillEntry @@ -364,6 +358,9 @@ class UserActor extends ReflectiveRoleableActor { } val now = TimeHelpers.nowMillis() + // TODO: Review this and its usage in user state. + // TODO: The assumption is that the resource set increases all the time, + // TODO: so the current map contains everything ever known (assuming we do not run backwards in time). val currentResourcesMap = aquarium.currentResourceTypesMap val chargingReason = RealtimeChargingReason(None, now) diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala index 7e80e69..2bd1364 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala @@ -36,95 +36,75 @@ package gr.grnet.aquarium.charging import gr.grnet.aquarium.policy.{ResourceType, EffectivePriceTable, FullPriceTable} -import scala.collection.mutable import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.logic.accounting.dsl.Timeslot import gr.grnet.aquarium.Aquarium import gr.grnet.aquarium.computation.BillingMonthInfo -import gr.grnet.aquarium.charging.state.AgreementHistory +import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, AgreementHistoryModel, WorkingResourcesChargingState} import gr.grnet.aquarium.charging.wallet.WalletEntry -import com.ckkloverdos.key.TypedKeySkeleton +import scala.collection.mutable /** * A charging behavior indicates how charging for a resource will be done - * wrt the various states a resource instance can be. + * wrt the various states a resource instance can be in. * * @author Christos KK Loverdos */ trait ChargingBehavior { - def alias: String + def selectorLabelsHierarchy: List[String] + + /** + * Provides some initial charging details that will be part of the mutable charging state + * ([[gr.grnet.aquarium.charging.state.WorkingResourcesChargingState]]). + */ + def initialChargingDetails: Map[String, Any] - def inputs: Set[ChargingInput] + def computeSelectorPath( + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + currentResourceEvent: ResourceEventModel, + referenceTimeslot: Timeslot, + totalCredits: Double + ): List[String] def selectEffectivePriceTable( fullPriceTable: FullPriceTable, - chargingData: mutable.Map[String, Any], + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, currentResourceEvent: ResourceEventModel, referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double + totalCredits: Double ): EffectivePriceTable - /** * * @return The number of wallet entries recorded and the new total credits */ - def chargeResourceEvent( + def processResourceEvent( aquarium: Aquarium, currentResourceEvent: ResourceEventModel, resourceType: ResourceType, billingMonthInfo: BillingMonthInfo, - previousResourceEventOpt: Option[ResourceEventModel], - userAgreements: AgreementHistory, - chargingData: mutable.Map[String, Any], + workingResourcesChargingState: WorkingResourcesChargingState, + userAgreements: AgreementHistoryModel, totalCredits: Double, walletEntryRecorder: WalletEntry ⇒ Unit ): (Int, Double) - def supportsImplicitEvents: Boolean - - def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean - - def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccuredMillis: Long): ResourceEventModel -} - -object ChargingBehavior { - final case class ChargingBehaviorKey[T: Manifest](override val name: String) extends TypedKeySkeleton[T](name) + def computeCreditsToSubtract( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + oldCredits: Double, + timeDeltaMillis: Long, + unitPrice: Double + ): (Double /* credits */, String /* explanation */) /** - * Keys used to save information between calls of `chargeResourceEvent` + * Given the charging state of a resource instance and the details of the incoming message, compute the new + * accumulating amount. */ - object EnvKeys { - final val ResourceInstanceAccumulatingAmount = ChargingBehaviorKey[Double]("resource.instance.accumulating.amount") - } - - def makeValueMapFor( - chargingBehavior: ChargingBehavior, - totalCredits: Double, - oldTotalAmount: Double, - newTotalAmount: Double, - timeDelta: Double, - previousValue: Double, - currentValue: Double, - unitPrice: Double - ): Map[ChargingInput, Any] = { - - val inputs = chargingBehavior.inputs - var map = Map[ChargingInput, Any]() - - if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.alias - if(inputs contains TotalCreditsInput ) map += TotalCreditsInput -> totalCredits - if(inputs contains OldTotalAmountInput ) map += OldTotalAmountInput -> oldTotalAmount - if(inputs contains NewTotalAmountInput ) map += NewTotalAmountInput -> newTotalAmount - if(inputs contains TimeDeltaInput ) map += TimeDeltaInput -> timeDelta - if(inputs contains PreviousValueInput ) map += PreviousValueInput -> previousValue - if(inputs contains CurrentValueInput ) map += CurrentValueInput -> currentValue - if(inputs contains UnitPriceInput ) map += UnitPriceInput -> unitPrice - - map - } + def computeNewAccumulatingAmount( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + eventDetails: Map[String, String] + ): Double } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala index 993fe62..9a5551e 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala @@ -41,16 +41,13 @@ import scala.collection.mutable import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.{Aquarium, AquariumInternalError} import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType} -import com.ckkloverdos.key.TypedKey import gr.grnet.aquarium.util._ -import gr.grnet.aquarium.util.LogHelpers.Debug import gr.grnet.aquarium.util.date.TimeHelpers import gr.grnet.aquarium.charging.wallet.WalletEntry import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo} -import gr.grnet.aquarium.charging.state.AgreementHistory +import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel} import gr.grnet.aquarium.logic.accounting.dsl.Timeslot import gr.grnet.aquarium.store.PolicyStore -import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys /** * A charging behavior indicates how charging for a resource will be done @@ -60,113 +57,80 @@ import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys */ abstract class ChargingBehaviorSkeleton( - final val alias: String, - final val inputs: Set[ChargingInput], - final val selectorHierarchy: List[List[String]] = Nil + final val selectorLabelsHierarchy: List[String] ) extends ChargingBehavior with Loggable { - final val inputNames = inputs.map(_.name) - - @inline private[this] def hrs(millis: Double) = { + protected def hrs(millis: Double) = { val hours = millis / 1000 / 60 / 60 val roundedHours = hours roundedHours } - protected def computeCreditsToSubtract( - oldCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double, - timeDeltaMillis: Long, - previousValue: Double, - currentValue: Double, - unitPrice: Double, - details: Map[String, String] - ): (Double, String) = { - alias match { - case ChargingBehaviorAliases.continuous ⇒ - val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice - val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format( - hrs(timeDeltaMillis), - oldAccumulatingAmount, - unitPrice - ) - - (credits, explanation) - - case ChargingBehaviorAliases.vmtime ⇒ - val credits = hrs(timeDeltaMillis) * unitPrice - val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice) - - (credits, explanation) - - case ChargingBehaviorAliases.once ⇒ - val credits = currentValue - val explanation = "Value(%s)".format(currentValue) - - (credits, explanation) - - case name ⇒ - throw new AquariumInternalError("Cannot compute credit diff for charging behavior %s".format(name)) - } - } - protected def rcDebugInfo(rcEvent: ResourceEventModel) = { rcEvent.toDebugString } - protected def computeSelectorPath( - chargingData: mutable.Map[String, Any], - currentResourceEvent: ResourceEventModel, - referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double - ): List[String] + protected def newWorkingResourceInstanceChargingState() = { + new WorkingResourceInstanceChargingState( + mutable.Map(), + Nil, + Nil, + 0.0, + 0.0, + 0.0, + 0.0 + ) + } - /** - * - * @param chargingData - * @param previousResourceEventOpt - * @param currentResourceEvent - * @param billingMonthInfo - * @param referenceTimeslot - * @param resourceType - * @param agreementByTimeslot - * @param previousValue - * @param totalCredits - * @param policyStore - * @param walletEntryRecorder - * @return The number of wallet entries recorded and the new total credits - */ - protected def computeChargeslots( - chargingData: mutable.Map[String, Any], - previousResourceEventOpt: Option[ResourceEventModel], - currentResourceEvent: ResourceEventModel, + final protected def ensureWorkingState( + workingResourcesChargingState: WorkingResourcesChargingState, + resourceEvent: ResourceEventModel + ) { + ensureResourcesChargingStateDetails(workingResourcesChargingState.details) + ensureResourceInstanceChargingState(workingResourcesChargingState, resourceEvent) + } + + protected def ensureResourcesChargingStateDetails( + details: mutable.Map[String, Any] + ) {} + + protected def ensureResourceInstanceChargingState( + workingResourcesChargingState: WorkingResourcesChargingState, + resourceEvent: ResourceEventModel + ) { + + val instanceID = resourceEvent.instanceID + val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance + + stateOfResourceInstance.get(instanceID) match { + case None ⇒ + stateOfResourceInstance(instanceID) = newWorkingResourceInstanceChargingState() + + case _ ⇒ + } + } + + protected def computeWalletEntriesForNewEvent( + resourceEvent: ResourceEventModel, + resourceType: ResourceType, billingMonthInfo: BillingMonthInfo, + totalCredits: Double, referenceTimeslot: Timeslot, - resourceType: ResourceType, agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel], - previousValue: Double, - totalCredits: Double, + workingResourcesChargingStateDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, policyStore: PolicyStore, walletEntryRecorder: WalletEntry ⇒ Unit ): (Int, Double) = { - val currentValue = currentResourceEvent.value - val userID = currentResourceEvent.userID - val currentDetails = currentResourceEvent.details - - var _oldAccumulatingAmount = getChargingData( - chargingData, - EnvKeys.ResourceInstanceAccumulatingAmount - ).getOrElse(getResourceInstanceInitialAmount) + val userID = resourceEvent.userID + val resourceEventDetails = resourceEvent.details var _oldTotalCredits = totalCredits - var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails) - setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount) + var _newAccumulatingAmount = computeNewAccumulatingAmount(workingResourceInstanceChargingState, resourceEventDetails) + // It will also update the old one inside the data structure. + workingResourceInstanceChargingState.setNewAccumulatingAmount(_newAccumulatingAmount) val policyByTimeslot = policyStore.loadAndSortPoliciesWithin( referenceTimeslot.from.getTime, @@ -176,19 +140,16 @@ abstract class ChargingBehaviorSkeleton( val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ { this.selectEffectivePriceTable( fullPriceTable, - chargingData, - currentResourceEvent, + workingResourcesChargingStateDetails, + workingResourceInstanceChargingState, + resourceEvent, referenceTimeslot, - previousValue, - totalCredits, - _oldAccumulatingAmount, - _newAccumulatingAmount + totalCredits ) } val initialChargeslots = TimeslotComputations.computeInitialChargeslots( referenceTimeslot, - resourceType, policyByTimeslot, agreementByTimeslot, effectivePriceTableSelector @@ -199,14 +160,10 @@ abstract class ChargingBehaviorSkeleton( val timeDeltaMillis = stopMillis - startMillis val (creditsToSubtract, explanation) = this.computeCreditsToSubtract( - _oldTotalCredits, // FIXME ??? Should recalculate ??? - _oldAccumulatingAmount, // FIXME ??? Should recalculate ??? - _newAccumulatingAmount, // FIXME ??? Should recalculate ??? + workingResourceInstanceChargingState, + _oldTotalCredits, // FIXME ??? Should recalculate ??? timeDeltaMillis, - previousValue, - currentValue, - unitPrice, - currentDetails + unitPrice ) val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation) @@ -214,7 +171,7 @@ abstract class ChargingBehaviorSkeleton( } if(fullChargeslots.length == 0) { - throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id)) + throw new AquariumInternalError("No chargeslots computed for resource event %s".format(resourceEvent.id)) } val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum @@ -230,9 +187,9 @@ abstract class ChargingBehaviorSkeleton( billingMonthInfo.year, billingMonthInfo.month, fullChargeslots, - previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)), + resourceEvent :: workingResourceInstanceChargingState.previousEvents, resourceType, - currentResourceEvent.isSynthetic + resourceEvent.isSynthetic ) logger.debug("newWalletEntry = {}", newWalletEntry.toJsonString) @@ -242,50 +199,22 @@ abstract class ChargingBehaviorSkeleton( (1, newTotalCredits) } - protected def removeChargingData[T: Manifest]( - chargingData: mutable.Map[String, Any], - envKey: TypedKey[T] - ) = { - - chargingData.remove(envKey.name).asInstanceOf[Option[T]] - } - - protected def getChargingData[T: Manifest]( - chargingData: mutable.Map[String, Any], - envKey: TypedKey[T] - ) = { - - chargingData.get(envKey.name).asInstanceOf[Option[T]] - } - - protected def setChargingData[T: Manifest]( - chargingData: mutable.Map[String, Any], - envKey: TypedKey[T], - value: T - ) = { - - chargingData(envKey.name) = value - } def selectEffectivePriceTable( fullPriceTable: FullPriceTable, - chargingData: mutable.Map[String, Any], + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, currentResourceEvent: ResourceEventModel, referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double + totalCredits: Double ): EffectivePriceTable = { val selectorPath = computeSelectorPath( - chargingData, + workingChargingBehaviorDetails, + workingResourceInstanceChargingState, currentResourceEvent, referenceTimeslot, - previousValue, - totalCredits, - oldAccumulatingAmount, - newAccumulatingAmount + totalCredits ) fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource) @@ -295,30 +224,21 @@ abstract class ChargingBehaviorSkeleton( * A generic implementation for charging a resource event. * TODO: Ditch this in favor of completely ahdoc behaviors. * - * @param aquarium - * @param currentResourceEvent - * @param resourceType - * @param billingMonthInfo - * @param previousResourceEventOpt - * @param userAgreements - * @param chargingData - * @param totalCredits - * @param walletEntryRecorder * @return The number of wallet entries recorded and the new total credits */ - def chargeResourceEvent( + def processResourceEvent( aquarium: Aquarium, currentResourceEvent: ResourceEventModel, resourceType: ResourceType, billingMonthInfo: BillingMonthInfo, - previousResourceEventOpt: Option[ResourceEventModel], - userAgreements: AgreementHistory, - chargingData: mutable.Map[String, Any], + workingResourcesChargingState: WorkingResourcesChargingState, + userAgreements: AgreementHistoryModel, totalCredits: Double, walletEntryRecorder: WalletEntry ⇒ Unit ): (Int, Double) = { + (0,0) - val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent) + /*val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent) val isBillable = this.isBillableEvent(currentResourceEvent) val retval = if(!isBillable) { @@ -393,7 +313,7 @@ abstract class ChargingBehaviorSkeleton( Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1), resourceType, userAgreements.agreementByTimeslot, - this.getResourceInstanceUndefinedAmount, + 0.0, totalCredits, aquarium.policyStore, walletEntryRecorder @@ -401,112 +321,21 @@ abstract class ChargingBehaviorSkeleton( } } - retval - } - - /** - * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]] - * and the value the respective value. This map will be used to do the actual credit charge calculation - * by the respective algorithm. - * - * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context - * has been validated before the call to `makeValueMap` is made. - * - * @param totalCredits the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]] - * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]] - * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]] - * @param timeDelta the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]] - * @param previousValue the value for [[gr.grnet.aquarium.charging.PreviousValueInput]] - * @param currentValue the value for [[gr.grnet.aquarium.charging.CurrentValueInput]] - * @param unitPrice the value for [[gr.grnet.aquarium.charging.UnitPriceInput]] - * - * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values. - */ - def makeValueMap( - totalCredits: Double, - oldTotalAmount: Double, - newTotalAmount: Double, - timeDelta: Double, - previousValue: Double, - currentValue: Double, - unitPrice: Double - ): Map[ChargingInput, Any] = { - - ChargingBehavior.makeValueMapFor( - this, - totalCredits, - oldTotalAmount, - newTotalAmount, - timeDelta, - previousValue, - currentValue, - unitPrice) + retval*/ } - def needsPreviousEventForCreditAndAmountCalculation: Boolean = { - // If we need any variable that is related to the previous event - // then we do need a previous event - inputs.exists(_.isDirectlyRelatedToPreviousEvent) - } /** - * Given the old amount of a resource instance, the value arriving in a new resource event and the new details, - * compute the new instance amount. - * - * Note that the `oldAmount` does not make sense for all types of [[gr.grnet.aquarium.charging.ChargingBehavior]], - * in which case it is ignored. - * - * @param oldAccumulatingAmount the old accumulating amount - * @param newEventValue the value contained in a newly arrived - * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]] - * @param newDetails the `details` of the newly arrived - * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]] - * @return + * Given the charging state of a resource instance and the details of the incoming message, compute the new + * accumulating amount. */ def computeNewAccumulatingAmount( - oldAccumulatingAmount: Double, - newEventValue: Double, - newDetails: Map[String, String] + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + eventDetails: Map[String, String] ): Double - /** - * The initial amount. - */ - def getResourceInstanceInitialAmount: Double - - /** - * The amount used when no amount is meant to be relevant. - * - * For example, when there is no need for a previous event but an API requires the amount of the previous event. - * - * Normally, this value will never be used by client code (= charge computation code). - */ - def getResourceInstanceUndefinedAmount: Double = Double.NaN - - /** - * An event carries enough info to characterize it as billable or not. - * - * Typically all events are billable by default and indeed this is the default implementation - * provided here. - * - * The only exception to the rule is ON events for [[gr.grnet.aquarium.charging.VMChargingBehavior]]. - */ - def isBillableEvent(event: ResourceEventModel): Boolean = true - - /** - * This is called when we have the very first event for a particular resource instance, and we want to know - * if it is billable or not. - */ - def isBillableFirstEvent(event: ResourceEventModel): Boolean - - def mustGenerateDummyFirstEvent: Boolean - - def dummyFirstEventValue: Double = 0.0 // FIXME read from configuration def constructDummyFirstEventFor(actualFirst: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel = { - if(!mustGenerateDummyFirstEvent) { - throw new AquariumInternalError("constructDummyFirstEventFor() Not compliant with %s", this) - } val newDetails = Map( ResourceEventModel.Names.details_aquarium_is_synthetic -> "true", @@ -515,22 +344,6 @@ abstract class ChargingBehaviorSkeleton( ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty ) - actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, newOccurredMillis) + actualFirst.withDetailsAndValue(newDetails, 0.0, newOccurredMillis) } - - /** - * There are resources (cost policies) for which implicit events must be generated at the end of the billing period - * and also at the beginning of the next one. For these cases, this method must return `true`. - * - * The motivating example comes from the [[gr.grnet.aquarium.charging.VMChargingBehavior]] for which we - * must implicitly assume `OFF` events at the end of the billing period and `ON` events at the beginning of the next - * one. - * - */ - def supportsImplicitEvents: Boolean - - def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean - - @throws(classOf[Exception]) - def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala deleted file mode 100644 index 5d9a8f6..0000000 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingInput.scala +++ /dev/null @@ -1,118 +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.charging - -/** - * An input that is used in a charging function. - * - * @author Christos KK Loverdos - */ - -sealed abstract class ChargingInput( - val name: String, - val isDirectlyRelatedToPreviousEvent: Boolean = false, - val isDirectlyRelatedToCurrentEvent: Boolean = false -) - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the name of the cost - * policy for which a cost computation applies. - * - * @author Christos KK Loverdos - */ -case object ChargingBehaviorNameInput extends ChargingInput("chargingBehaviorName") - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the total credits. - * - * @author Christos KK Loverdos - */ -case object TotalCreditsInput extends ChargingInput("totalCredits") - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the old total (accumulating) - * amount, that is the resource amount before taking into account a new resource event. - * For example, in the case of `diskspace`, this is the total diskspace used by a user. - * - * @author Christos KK Loverdos - */ -case object OldTotalAmountInput extends ChargingInput("oldTotalAmount") - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the new total (accumulating) - * amount, that is the resource amount after taking into account a new resource event. - * For example, in the case of `diskspace`, this is the total diskspace used by a user. - * - * @author Christos KK Loverdos - */ -case object NewTotalAmountInput extends ChargingInput("newTotalAmount") - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the time delta between two - * consecutive resource events of the same type (same `resource` and `instanceID`). Time is measured in milliseconds. - * - * @author Christos KK Loverdos - */ -case object TimeDeltaInput extends ChargingInput("timeDelta", true, true) - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the `value` of the previous - * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]. - * - * @author Christos KK Loverdos - */ -case object PreviousValueInput extends ChargingInput("previousValue", true, false) - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the `value` of the current - * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]. - * - * @author Christos KK Loverdos - */ -case object CurrentValueInput extends ChargingInput("currentValue", false, true) - -/** - * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the unit price. - * - * @author Christos KK Loverdos - */ -case object UnitPriceInput extends ChargingInput("unitPrice") - -case object DetailsInput extends ChargingInput("details") - - - - diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala index d87035e..f2aa992 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala @@ -35,17 +35,16 @@ package gr.grnet.aquarium.charging +import scala.collection.mutable import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.computation.BillingMonthInfo -import gr.grnet.aquarium.charging.state.UserStateBootstrap +import gr.grnet.aquarium.charging.state.{WorkingResourcesChargingState, UserStateBootstrap, WorkingUserState, UserStateModel, StdUserState} import gr.grnet.aquarium.policy.ResourceType import gr.grnet.aquarium.util.{Lifecycle, Loggable} import gr.grnet.aquarium.util.LogHelpers.Debug import gr.grnet.aquarium.util.LogHelpers.Warn -import gr.grnet.aquarium.util.LogHelpers.DebugSeq import gr.grnet.aquarium.util.date.{MutableDateCalc, TimeHelpers} import gr.grnet.aquarium.{AquariumInternalError, AquariumAwareSkeleton} -import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel, StdUserState} import gr.grnet.aquarium.charging.reason.{MonthlyBillChargingReason, InitialUserStateSetup, ChargingReason} /** @@ -59,9 +58,9 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo lazy val resourceEventStore = aquarium.resourceEventStore //+ Lifecycle - def start() = () + def start() {} - def stop() = () + def stop() {} //- Lifecycle @@ -94,7 +93,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo true, billingMonthInfo.year, billingMonthInfo.month, - None + "" ) // We always save the state when it is a full month billing @@ -176,7 +175,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo } } /** - * Processes one resource event and computes relevant charges. + * Processes one resource event and computes relevant, incremental charges. * * @param resourceEvent * @param workingUserState @@ -189,37 +188,53 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo chargingReason: ChargingReason, billingMonthInfo: BillingMonthInfo, updateLatestMillis: Boolean - ): Unit = { + ): Boolean = { val resourceTypeName = resourceEvent.resource val resourceTypeOpt = workingUserState.findResourceType(resourceTypeName) if(resourceTypeOpt.isEmpty) { - return + // Unknown (yet) resource, ignoring event. + return false } val resourceType = resourceTypeOpt.get - val resourceAndInstanceInfo = resourceEvent.safeResourceInstanceInfo val chargingBehavior = aquarium.chargingBehaviorOf(resourceType) + val workingResourcesState = workingUserState.workingStateOfResources.get(resourceTypeName) match { + case Some(existingState) ⇒ + existingState + + case None ⇒ + // First time for this ChargingBehavior. + val newState = new WorkingResourcesChargingState( + details = mutable.Map(chargingBehavior.initialChargingDetails.toSeq:_*), + stateOfResourceInstance = mutable.Map() + ) + + workingUserState.workingStateOfResources(resourceTypeName) = newState + newState + } - val (walletEntriesCount, newTotalCredits) = chargingBehavior.chargeResourceEvent( + val m0 = TimeHelpers.nowMillis() + val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent( aquarium, resourceEvent, resourceType, billingMonthInfo, - workingUserState.previousEventOfResourceInstance.get(resourceAndInstanceInfo), - workingUserState.workingAgreementHistory.toAgreementHistory, - workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo), + workingResourcesState, + workingUserState.workingAgreementHistory, workingUserState.totalCredits, workingUserState.walletEntries += _ ) + val m1 = TimeHelpers.nowMillis() if(updateLatestMillis) { - workingUserState.latestUpdateMillis = TimeHelpers.nowMillis() + workingUserState.latestUpdateMillis = m1 } workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis) - workingUserState.previousEventOfResourceInstance(resourceAndInstanceInfo) = resourceEvent workingUserState.totalCredits = newTotalCredits + + true } def processResourceEvents( @@ -341,7 +356,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo billingMonthInfo.toShortDebugString ) - if(isFullMonthBilling) { + /*if(isFullMonthBilling) { // For the remaining events which must contribute an implicit OFF, we collect those OFFs // ... in order to generate an implicit ON later (during the next billing cycle). val (generatorsOfImplicitEnds, theirImplicitEnds) = workingUserState.findAndRemoveGeneratorsOfImplicitEndEvents( @@ -371,7 +386,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo workingUserState.walletEntries ++= specialWorkingUserState.walletEntries workingUserState.totalCredits = specialWorkingUserState.totalCredits - } + }*/ workingUserState } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala index 0dcfeba..18ff42c 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala @@ -36,8 +36,13 @@ package gr.grnet.aquarium.charging import gr.grnet.aquarium.event.model.resource.ResourceEventModel -import scala.collection.mutable import gr.grnet.aquarium.logic.accounting.dsl.Timeslot +import gr.grnet.aquarium.policy.ResourceType +import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState} +import scala.collection.mutable +import gr.grnet.aquarium.Aquarium +import gr.grnet.aquarium.computation.BillingMonthInfo +import gr.grnet.aquarium.charging.wallet.WalletEntry /** * In practice a resource usage will be charged for the total amount of usage @@ -47,64 +52,66 @@ import gr.grnet.aquarium.logic.accounting.dsl.Timeslot * * @author Christos KK Loverdos */ -final class ContinuousChargingBehavior - extends ChargingBehaviorSkeleton( - ChargingBehaviorAliases.continuous, - Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) { +final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) { - protected def computeSelectorPath( - chargingData: mutable.Map[String, Any], - currentResourceEvent: ResourceEventModel, - referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double - ): List[String] = { - Nil - } + def computeCreditsToSubtract( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + oldCredits: Double, + timeDeltaMillis: Long, + unitPrice: Double + ): (Double /* credits */, String /* explanation */) = { - def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = { - // If the total is in the details, get it, or else compute it - details.get("total") match { - case Some(total) ⇒ - total.toDouble + val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount + val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice + val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format( + hrs(timeDeltaMillis), + oldAccumulatingAmount, + unitPrice + ) - case _ ⇒ - oldAmount + newEventValue - } - } + (credits, explanation) - def getResourceInstanceInitialAmount: Double = { - 0.0 } - /** - * This is called when we have the very first event for a particular resource instance, and we want to know - * if it is billable or not. - */ - def isBillableFirstEvent(event: ResourceEventModel) = { - true + def computeSelectorPath( + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + currentResourceEvent: ResourceEventModel, + referenceTimeslot: Timeslot, + totalCredits: Double + ): List[String] = { + Nil } - def mustGenerateDummyFirstEvent = true - - def supportsImplicitEvents = { - true - } + def initialChargingDetails: Map[String, Any] = Map() - def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = { - true + def computeNewAccumulatingAmount( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + eventDetails: Map[String, String] + ): Double = { + workingResourceInstanceChargingState.oldAccumulatingAmount + + workingResourceInstanceChargingState.currentValue } def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = { - assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent)) - val details = resourceEvent.details val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details) resourceEvent.withDetails(newDetails, newOccurredMillis) } + + override def processResourceEvent( + aquarium: Aquarium, + currentResourceEvent: ResourceEventModel, + resourceType: ResourceType, + billingMonthInfo: BillingMonthInfo, + workingResourcesChargingState: WorkingResourcesChargingState, + userAgreements: AgreementHistoryModel, + totalCredits: Double, + walletEntryRecorder: WalletEntry ⇒ Unit + ): (Int, Double) = { + (0,0) + } } object ContinuousChargingBehavior { diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala index cbb66bc..c1637ac 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala @@ -36,10 +36,13 @@ package gr.grnet.aquarium.charging import gr.grnet.aquarium.event.model.resource.ResourceEventModel -import gr.grnet.aquarium.AquariumException +import gr.grnet.aquarium.{Aquarium, AquariumException} import scala.collection.mutable import gr.grnet.aquarium.logic.accounting.dsl.Timeslot -import gr.grnet.aquarium.policy.FullPriceTable +import gr.grnet.aquarium.policy.{ResourceType, FullPriceTable} +import gr.grnet.aquarium.computation.BillingMonthInfo +import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, AgreementHistoryModel, WorkingResourcesChargingState} +import gr.grnet.aquarium.charging.wallet.WalletEntry /** * A charging behavior for which resource events just carry a credit amount that will be added to the total one. @@ -48,44 +51,77 @@ import gr.grnet.aquarium.policy.FullPriceTable * * @author Christos KK Loverdos */ -final class OnceChargingBehavior - extends ChargingBehaviorSkeleton( - ChargingBehaviorAliases.once, - Set(ChargingBehaviorNameInput, CurrentValueInput)) { +final class OnceChargingBehavior extends ChargingBehaviorSkeleton(Nil) { + def computeCreditsToSubtract( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + oldCredits: Double, + timeDeltaMillis: Long, + unitPrice: Double + ): (Double /* credits */, String /* explanation */) = { - protected def computeSelectorPath( - chargingData: mutable.Map[String, Any], + val currentValue = workingResourceInstanceChargingState.currentValue + val credits = currentValue + val explanation = "Value(%s)".format(currentValue) + + (credits, explanation) + } + + def computeSelectorPath( + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, currentResourceEvent: ResourceEventModel, referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double + totalCredits: Double ): List[String] = { List(FullPriceTable.DefaultSelectorKey) } - /** - * This is called when we have the very first event for a particular resource instance, and we want to know - * if it is billable or not. - */ - def isBillableFirstEvent(event: ResourceEventModel) = { - true - } - def mustGenerateDummyFirstEvent = false // no need to + override def processResourceEvent( + aquarium: Aquarium, + resourceEvent: ResourceEventModel, + resourceType: ResourceType, + billingMonthInfo: BillingMonthInfo, + workingResourcesChargingState: WorkingResourcesChargingState, + userAgreements: AgreementHistoryModel, + totalCredits: Double, + walletEntryRecorder: WalletEntry ⇒ Unit + ): (Int, Double) = { + // The credits are given in the value + // But we cannot just apply them, since we also need to take into account the unit price. + // Normally, the unit price is 1.0 but we have the flexibility to allow more stuff). - def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = { - oldAmount - } + val instanceID = resourceEvent.instanceID + val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance + + // 0. Ensure proper state per resource and per instance + ensureWorkingState(workingResourcesChargingState, resourceEvent) - def getResourceInstanceInitialAmount = 0.0 + // 1. Find the unit price at the moment of the event - def supportsImplicitEvents = false + val workingResourcesChargingStateDetails = workingResourcesChargingState.details + val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID) + + computeWalletEntriesForNewEvent( + resourceEvent, + resourceType, + billingMonthInfo, + totalCredits, + Timeslot(resourceEvent.occurredMillis, resourceEvent.occurredMillis + 1), // single point in time + userAgreements.agreementByTimeslot, + workingResourcesChargingStateDetails, + workingResourceInstanceChargingState, + aquarium.policyStore, + walletEntryRecorder + ) + } - def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false + def initialChargingDetails: Map[String, Any] = Map() - def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = { - throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this)) + def computeNewAccumulatingAmount( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + eventDetails: Map[String, String] + ): Double = { + workingResourceInstanceChargingState.oldAccumulatingAmount } } diff --git a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala index 0f43afb..a6b70ff 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala @@ -35,76 +35,58 @@ package gr.grnet.aquarium.charging -import gr.grnet.aquarium.AquariumInternalError +import gr.grnet.aquarium.{Aquarium, AquariumInternalError} import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.logic.accounting.dsl.Timeslot -import scala.collection.mutable import VMChargingBehavior.Selectors.Power +import VMChargingBehavior.SelectorLabels.PowerStatus +import gr.grnet.aquarium.policy.ResourceType +import gr.grnet.aquarium.computation.BillingMonthInfo +import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel} +import gr.grnet.aquarium.charging.wallet.WalletEntry +import scala.collection.mutable /** * The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage. * * @author Christos KK Loverdos */ -final class VMChargingBehavior - extends ChargingBehaviorSkeleton( - ChargingBehaviorAliases.vmtime, - Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput), - List(List(Power.powerOn, Power.powerOff))) { - - protected def computeSelectorPath( - chargingData: mutable.Map[String, Any], - currentResourceEvent: ResourceEventModel, - referenceTimeslot: Timeslot, - previousValue: Double, - totalCredits: Double, - oldAccumulatingAmount: Double, - newAccumulatingAmount: Double - ): List[String] = { - // FIXME - List(Power.powerOn) // compute prices for power-on state - } - /** - * - * @param oldAmount is ignored - * @param newEventValue - * @return - */ - def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = { - newEventValue - } +final class VMChargingBehavior extends ChargingBehaviorSkeleton(List(PowerStatus)) { + def computeCreditsToSubtract( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + oldCredits: Double, + timeDeltaMillis: Long, + unitPrice: Double + ): (Double /* credits */, String /* explanation */) = { - def getResourceInstanceInitialAmount: Double = { - 0.0 - } + val credits = hrs(timeDeltaMillis) * unitPrice + val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice) - override def isBillableEvent(event: ResourceEventModel) = { - // ON events do not contribute, only OFF ones. - VMChargingBehaviorValues.isOFFValue(event.value) - } + (credits, explanation) - /** - * This is called when we have the very first event for a particular resource instance, and we want to know - * if it is billable or not. - */ - def isBillableFirstEvent(event: ResourceEventModel) = { - false } - def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs - - def supportsImplicitEvents = { - true + def computeSelectorPath( + workingChargingBehaviorDetails: mutable.Map[String, Any], + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + currentResourceEvent: ResourceEventModel, + referenceTimeslot: Timeslot, + totalCredits: Double + ): List[String] = { + // FIXME + List(Power.powerOn) // compute prices for power-on state } - def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = { - // If we have ON events with no OFF companions at the end of the billing period, - // then we must generate implicit OFF events. - VMChargingBehaviorValues.isONValue(resourceEvent.value) + def initialChargingDetails: Map[String, Any] = Map() + + def computeNewAccumulatingAmount( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + eventDetails: Map[String, String] + ): Double = { + workingResourceInstanceChargingState.currentValue } def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = { - assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent)) assert(VMChargingBehaviorValues.isONValue(resourceEvent.value)) val details = resourceEvent.details @@ -117,9 +99,33 @@ final class VMChargingBehavior def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = { throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this)) } + + /** + * + * @return The number of wallet entries recorded and the new total credits + */ + override def processResourceEvent( + aquarium: Aquarium, + currentResourceEvent: ResourceEventModel, + resourceType: ResourceType, + billingMonthInfo: BillingMonthInfo, + workingState: WorkingResourcesChargingState, + userAgreements: AgreementHistoryModel, + totalCredits: Double, + walletEntryRecorder: WalletEntry ⇒ Unit + ): (Int, Double) = { + + + (0,0) + } + } object VMChargingBehavior { + object SelectorLabels { + final val PowerStatus = "Power Status (ON/OFF)" + } + object Selectors { object Power { // When the VM is powered on diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistory.scala b/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistory.scala index 96b2a48..5572b9c 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistory.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistory.scala @@ -45,7 +45,7 @@ import gr.grnet.aquarium.policy.UserAgreementModel * @author Christos KK Loverdos */ -case class AgreementHistory(agreements: List[UserAgreementModel]) { +case class AgreementHistory(agreements: List[UserAgreementModel]) extends AgreementHistoryModel { def toWorkingAgreementHistory = { (new WorkingAgreementHistory) ++ agreements } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala b/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistoryModel.scala similarity index 81% rename from src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala rename to src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistoryModel.scala index ac846b0..2423438 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/AgreementHistoryModel.scala @@ -33,15 +33,20 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.charging +package gr.grnet.aquarium.charging.state + +import gr.grnet.aquarium.policy.UserAgreementModel +import scala.collection.immutable +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot /** * * @author Christos KK Loverdos */ +trait AgreementHistoryModel { + def size = agreements.size + + def agreements: Traversable[UserAgreementModel] -object ChargingBehaviorAliases { - final val vmtime = "vmtime" - final val continuous = "continuous" - final val once = "once" + def agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel] } diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala new file mode 100644 index 0000000..03d493b --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingState.scala @@ -0,0 +1,70 @@ +/* + * 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.charging.state + +import scala.collection.mutable + +import gr.grnet.aquarium.event.model.resource.ResourceEventModel + +/** + * + * @author Christos KK Loverdos + */ +case class ResourceInstanceChargingState( + details: Map[String, Any], + previousEvents: List[ResourceEventModel], + // the implicitly issued resource event at the beginning of the billing period. + implicitlyIssuedStartEvent: List[ResourceEventModel], + accumulatingAmount: Double, + oldAccumulatingAmount: Double, + previousValue: Double, + currentValue: Double +) extends ResourceInstanceChargingStateModel { + + def mutableDetails = mutable.Map(this.details.toSeq:_*) + + def toWorkingResourceInstanceChargingState = { + new WorkingResourceInstanceChargingState( + details = mutableDetails, + previousEvents = this.previousEvents, + implicitlyIssuedStartEvent = this.implicitlyIssuedStartEvent, + accumulatingAmount = this.accumulatingAmount, + oldAccumulatingAmount = this.oldAccumulatingAmount, + previousValue = this.previousValue, + currentValue = this.currentValue + ) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/CostPolicyAlgorithmCompiler.scala b/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingStateModel.scala similarity index 74% rename from src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/CostPolicyAlgorithmCompiler.scala rename to src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingStateModel.scala index 3c880aa..b6f1f72 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/CostPolicyAlgorithmCompiler.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/ResourceInstanceChargingStateModel.scala @@ -33,23 +33,28 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.logic.accounting.algorithm - -import com.ckkloverdos.maybe.{Just, Maybe} +package gr.grnet.aquarium.charging.state +import gr.grnet.aquarium.event.model.resource.ResourceEventModel /** - * Compiles the textual representation of a cost policy charging algorithm to an executable form. * * @author Christos KK Loverdos */ +trait ResourceInstanceChargingStateModel { + def details: scala.collection.Map[String, Any] + + def previousEvents: List[ResourceEventModel] + + // the implicitly issued resource event at the beginning of the billing period. + def implicitlyIssuedStartEvent: List[ResourceEventModel] + + // Always the new accumulating amount + def accumulatingAmount: Double + + def oldAccumulatingAmount: Double + + def previousValue: Double -trait CostPolicyAlgorithmCompiler { - /** - * Compiles the textual representation of a cost policy charging algorithm to an executable form. - * - * @param definition the textual representation of the algorithm - * @return the executable form of the algorithm - */ - def compile(definition: String): ExecutableChargingBehaviorAlgorithm + def currentValue: Double } diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/ExecutableChargingBehaviorAlgorithm.scala b/src/main/scala/gr/grnet/aquarium/charging/state/ResourcesChargingState.scala similarity index 71% rename from src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/ExecutableChargingBehaviorAlgorithm.scala rename to src/main/scala/gr/grnet/aquarium/charging/state/ResourcesChargingState.scala index 782b7cc..7bee704 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/ExecutableChargingBehaviorAlgorithm.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/ResourcesChargingState.scala @@ -33,14 +33,31 @@ * or implied, of GRNET S.A. */ -package gr.grnet.aquarium.logic.accounting.algorithm - -import gr.grnet.aquarium.charging.ChargingInput +package gr.grnet.aquarium.charging.state +import scala.collection.mutable /** - * An charging algorithm in executable form. * * @author Christos KK Loverdos */ -trait ExecutableChargingBehaviorAlgorithm extends (Map[ChargingInput, Any] ⇒ Double) +case class ResourcesChargingState( + details: Map[String, Any], + stateOfResourceInstance: Map[String /* InstanceID */, ResourceInstanceChargingState] +) { + + def mutableDetails = mutable.Map(this.details.toSeq:_*) + + def mutableStateOfResourceInstance = mutable.Map(( + for((k, v) ← this.stateOfResourceInstance) + yield (k, v.toWorkingResourceInstanceChargingState) + ).toSeq: _* + ) + + def toWorkingResourcesChargingState = { + new WorkingResourcesChargingState( + details = mutableDetails, + stateOfResourceInstance = mutableStateOfResourceInstance + ) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/StdUserState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/StdUserState.scala index e1e594f..e75f47c 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/StdUserState.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/StdUserState.scala @@ -36,7 +36,6 @@ package gr.grnet.aquarium.charging.state import gr.grnet.aquarium.policy.UserAgreementModel -import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.charging.wallet.WalletEntry import gr.grnet.aquarium.charging.reason.{InitialUserStateSetup, ChargingReason} import gr.grnet.aquarium.AquariumInternalError @@ -58,10 +57,7 @@ final case class StdUserState( billingYear: Int, billingMonth: Int, chargingReason: ChargingReason, - previousResourceEvents: List[ResourceEventModel], - implicitlyIssuedStartEvents: List[ResourceEventModel], - accumulatingAmountOfResourceInstance: Map[String, Double], - chargingDataOfResourceInstance: Map[String, Map[String, Any]], + stateOfResources: Map[String, ResourcesChargingState], billingPeriodOutOfSyncResourceEventsCounter: Long, agreementHistory: AgreementHistory, walletEntries: List[WalletEntry] @@ -122,9 +118,6 @@ final object StdUserState { bmi.year, bmi.month, chargingReason, - Nil, - Nil, - Map(), Map(), 0L, AgreementHistory.initial(initialAgreement), diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModel.scala b/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModel.scala index 44ad509..dc00baa 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModel.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModel.scala @@ -72,13 +72,7 @@ trait UserStateModel extends JsonSupport { def chargingReason: ChargingReason - def previousResourceEvents: List[ResourceEventModel] - - def implicitlyIssuedStartEvents: List[ResourceEventModel] - - def accumulatingAmountOfResourceInstance: Map[String, Double] - - def chargingDataOfResourceInstance: Map[String, Map[String, Any]] + def stateOfResources: Map[String, ResourcesChargingState] def billingPeriodOutOfSyncResourceEventsCounter: Long diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModelSkeleton.scala b/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModelSkeleton.scala index 2bdf896..b0326a7 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModelSkeleton.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/UserStateModelSkeleton.scala @@ -36,7 +36,6 @@ package gr.grnet.aquarium.charging.state import scala.collection.mutable -import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.charging.wallet.WalletEntry import gr.grnet.aquarium.policy.ResourceType @@ -47,33 +46,9 @@ import gr.grnet.aquarium.policy.ResourceType */ abstract class UserStateModelSkeleton extends UserStateModel { - protected def mutableMap[Vin, Vout]( - inputMap: Map[String, Vin], - vInOut: Vin ⇒ Vout - ): mutable.Map[(String, String), Vout] = { - val items = for { - (resourceAndInstanceID, vIn) ← inputMap.toSeq - } yield { - StdUserState.resourceAndInstanceIDOfString(resourceAndInstanceID) -> vInOut(vIn) - } - - mutable.Map(items: _*) - } - - protected def mutableAccumulatingAmountMap: mutable.Map[(String, String), Double] = { - mutableMap(accumulatingAmountOfResourceInstance, identity[Double]) - } - - protected def mutableChargingDataMap: mutable.Map[(String, String), mutable.Map[String, Any]] = { - mutableMap(chargingDataOfResourceInstance, (vIn: Map[String, Any]) ⇒ mutable.Map(vIn.toSeq: _*)) - } - - protected def mutableImplicitlyIssuedStartMap: mutable.Map[(String, String), ResourceEventModel] = { - mutable.Map(implicitlyIssuedStartEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*) - } - - protected def mutablePreviousEventsMap: mutable.Map[(String, String), ResourceEventModel] = { - mutable.Map(previousResourceEvents.map(rem ⇒ (rem.safeResource, rem.safeInstanceID) -> rem): _*) + protected def mutableStateOfChargingBehavior: mutable.Map[String, WorkingResourcesChargingState] = { + val contents = for((k, v) ← stateOfResources) yield (k, v.toWorkingResourcesChargingState) + mutable.Map(contents.toSeq: _*) } protected def mutableWalletEntries = { @@ -92,10 +67,7 @@ abstract class UserStateModelSkeleton extends UserStateModel { this.parentIDInStore, this.chargingReason, resourceTypesMap, - mutablePreviousEventsMap, - mutableImplicitlyIssuedStartMap, - mutableAccumulatingAmountMap, - mutableChargingDataMap, + mutableStateOfChargingBehavior, this.totalCredits, mutableAgreementHistory, this.occurredMillis, diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingAgreementHistory.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingAgreementHistory.scala index 2039a25..80aeff5 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingAgreementHistory.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingAgreementHistory.scala @@ -38,6 +38,7 @@ package gr.grnet.aquarium.charging.state import scala.collection.immutable import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, UserAgreementModel} import gr.grnet.aquarium.util.json.JsonSupport +import gr.grnet.aquarium.logic.accounting.dsl.Timeslot /** * @@ -46,9 +47,11 @@ import gr.grnet.aquarium.util.json.JsonSupport final case class WorkingAgreementHistory( var agreements: immutable.SortedSet[UserAgreementModel] = immutable.SortedSet[UserAgreementModel]() -) extends JsonSupport { +) extends AgreementHistoryModel with JsonSupport { - def size = agreements.size + def agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel] = { + immutable.TreeMap(agreements.map(ag ⇒ (ag.timeslot, ag)).toSeq: _*) + } def setFrom(that: WorkingAgreementHistory): this.type = { this.agreements = that.agreements diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala new file mode 100644 index 0000000..7cb207a --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala @@ -0,0 +1,82 @@ +/* + * 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.charging.state + +import scala.collection.mutable + +import gr.grnet.aquarium.event.model.resource.ResourceEventModel + +/** + * Working (mutable) state of a resource instance, that is a `(resourceType, instanceID)`. + * + * @author Christos KK Loverdos + */ +final class WorkingResourceInstanceChargingState( + val details: mutable.Map[String, Any], + var previousEvents: List[ResourceEventModel], + // the implicitly issued resource event at the beginning of the billing period. + var implicitlyIssuedStartEvent: List[ResourceEventModel], + // Always the new accumulating amount + var accumulatingAmount: Double, + var oldAccumulatingAmount: Double, + var previousValue: Double, + var currentValue: Double +) extends ResourceInstanceChargingStateModel { + + def toResourceInstanceChargingState = { + new ResourceInstanceChargingState( + details = immutableDetails, + previousEvents = this.previousEvents, + implicitlyIssuedStartEvent = this.implicitlyIssuedStartEvent, + accumulatingAmount = this.accumulatingAmount, + oldAccumulatingAmount = this.oldAccumulatingAmount, + previousValue = this.previousValue, + currentValue = this.currentValue + ) + } + + def immutableDetails = this.details.toMap + + def setNewAccumulatingAmount(amount: Double) { + this.oldAccumulatingAmount = this.accumulatingAmount + this.accumulatingAmount = amount + } + + def setNewCurrentValue(value: Double) { + this.previousValue = this.currentValue + this.currentValue = value + } +} diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala new file mode 100644 index 0000000..ed9f56b --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.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.charging.state + +import scala.collection.mutable + +/** + * Working (mutable state) for a resource instances of the same resource type. + * + * @param details Generic state related to the type of resource as a whole + * @param stateOfResourceInstance A map from `instanceID` to + * [[gr.grnet.aquarium.charging.state.WorkingResourceInstanceChargingState]]. + * + * @author Christos KK Loverdos + */ +final class WorkingResourcesChargingState( + val details: mutable.Map[String, Any], + val stateOfResourceInstance: mutable.Map[String, WorkingResourceInstanceChargingState] +) { + + def immutableDetails = Map(this.details.toSeq: _*) + + def immutableStateOfResourceInstance = Map(( + for((k, v) ← this.stateOfResourceInstance) + yield (k, v.toResourceInstanceChargingState) + ).toSeq:_*) + + def toResourcesChargingState = { + ResourcesChargingState( + details = immutableDetails, + stateOfResourceInstance = immutableStateOfResourceInstance + ) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingUserState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingUserState.scala index 0bff90e..7341b84 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingUserState.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingUserState.scala @@ -38,7 +38,6 @@ package gr.grnet.aquarium.charging.state import scala.collection.mutable import gr.grnet.aquarium.policy.ResourceType import gr.grnet.aquarium.event.model.resource.ResourceEventModel -import gr.grnet.aquarium.computation.BillingMonthInfo import gr.grnet.aquarium.charging.reason.ChargingReason import gr.grnet.aquarium.util.json.JsonSupport import gr.grnet.aquarium.charging.ChargingBehavior @@ -55,26 +54,13 @@ final class WorkingUserState( var parentUserStateIDInStore: Option[String], var chargingReason: ChargingReason, val resourceTypesMap: Map[String, ResourceType], - - /** - * 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. - */ - val previousEventOfResourceInstance: mutable.Map[(String, String), ResourceEventModel], - - /** - * the implicitly issued resource events at the beginning of the billing period. - */ - val implicitlyIssuedStartEventOfResourceInstance: mutable.Map[(String, String), ResourceEventModel], - val accumulatingAmountOfResourceInstance: mutable.Map[(String, String), Double], - val chargingDataOfResourceInstance: mutable.Map[(String, String), mutable.Map[String, Any]], + val workingStateOfResources: mutable.Map[String /* resourceType.name */, WorkingResourcesChargingState], var totalCredits: Double, val workingAgreementHistory: WorkingAgreementHistory, var latestUpdateMillis: Long, // last update of this working user state var latestResourceEventOccurredMillis: Long, var billingPeriodOutOfSyncResourceEventsCounter: Long, - val walletEntries: mutable.ListBuffer[WalletEntry] + val walletEntries: mutable.ListBuffer[WalletEntry] // FIXME: not all in memory ) extends JsonSupport { def updateLatestResourceEventOccurredMillis(millis: Long): Unit = { @@ -83,46 +69,24 @@ final class WorkingUserState( } } - private[this] def immutablePreviousResourceEvents: List[ResourceEventModel] = { - previousEventOfResourceInstance.valuesIterator.toList - } - - private[this] def immutableImplicitlyIssuedStartEvents: List[ResourceEventModel] = { - implicitlyIssuedStartEventOfResourceInstance.valuesIterator.toList - } - - private[this] def immutableAccumulatingAmountMap: Map[String, Double] = { - val items = for { - ((resource, instanceID), accumulatingAmount) ← accumulatingAmountOfResourceInstance.toSeq - } yield { - StdUserState.stringOfResourceAndInstanceID(resource, instanceID) -> accumulatingAmount - } - - Map(items: _*) - } - - private[this] def immutableChargingDataMap: Map[String, Map[String, Any]] = { - val items = for { - ((resource, instanceID), mapValue) ← chargingDataOfResourceInstance.toSeq - } yield { - StdUserState.stringOfResourceAndInstanceID(resource, instanceID) -> Map(mapValue.toSeq: _*) - } - - Map(items: _*) + def immutableAgreementHistory = { + this.workingAgreementHistory.toAgreementHistory } - private[this] def immutableAgreementHistory = { - this.workingAgreementHistory.toAgreementHistory + def immutableChargingBehaviorState = { + val contents = for((k, v) ← this.workingStateOfResources) yield (k, v.toResourcesChargingState) + Map(contents.toSeq:_*) } + // TODO: Connect this user state to an originating parent working user state (if applicable) => new attribute def toUserState( isFullBillingMonth: Boolean, billingYear: Int, billingMonth: Int, - idOpt: Option[String] + id: String ) = { new StdUserState( - idOpt.getOrElse(""), + id, this.parentUserStateIDInStore, this.userID, this.latestUpdateMillis, @@ -132,61 +96,58 @@ final class WorkingUserState( billingYear, billingMonth, this.chargingReason, - immutablePreviousResourceEvents, - immutableImplicitlyIssuedStartEvents, - immutableAccumulatingAmountMap, - immutableChargingDataMap, + immutableChargingBehaviorState, billingPeriodOutOfSyncResourceEventsCounter, immutableAgreementHistory, walletEntries.toList ) } - def newForImplicitEndsAsPreviousEvents( - previousResourceEvents: mutable.Map[(String, String), ResourceEventModel] - ) = { - - new WorkingUserState( - this.userID, - this.parentUserStateIDInStore, - this.chargingReason, - this.resourceTypesMap, - previousResourceEvents, - this.implicitlyIssuedStartEventOfResourceInstance, - this.accumulatingAmountOfResourceInstance, - this.chargingDataOfResourceInstance, - this.totalCredits, - this.workingAgreementHistory, - this.latestUpdateMillis, - this.latestResourceEventOccurredMillis, - this.billingPeriodOutOfSyncResourceEventsCounter, - this.walletEntries - ) - } +// def newForImplicitEndsAsPreviousEvents( +// previousResourceEvents: mutable.Map[(String, String), ResourceEventModel] +// ) = { +// +// new WorkingUserState( +// this.userID, +// this.parentUserStateIDInStore, +// this.chargingReason, +// this.resourceTypesMap, +// previousResourceEvents, +// this.implicitlyIssuedStartEventOfResourceInstance, +// this.accumulatingAmountOfResourceInstance, +// this.chargingDataOfResourceInstance, +// this.totalCredits, +// this.workingAgreementHistory, +// this.latestUpdateMillis, +// this.latestResourceEventOccurredMillis, +// this.billingPeriodOutOfSyncResourceEventsCounter, +// this.walletEntries +// ) +// } def findResourceType(name: String): Option[ResourceType] = { resourceTypesMap.get(name) } - def getChargingDataForResourceEvent(resourceAndInstanceInfo: (String, String)): mutable.Map[String, Any] = { - chargingDataOfResourceInstance.get(resourceAndInstanceInfo) match { - case Some(map) ⇒ - map - - case None ⇒ - val map = mutable.Map[String, Any]() - chargingDataOfResourceInstance(resourceAndInstanceInfo) = map - map - - } - } - - def setChargingDataForResourceEvent( - resourceAndInstanceInfo: (String, String), - data: mutable.Map[String, Any] - ): Unit = { - chargingDataOfResourceInstance(resourceAndInstanceInfo) = data - } +// def getChargingDataForResourceEvent(resourceAndInstanceInfo: (String, String)): mutable.Map[String, Any] = { +// chargingDataOfResourceInstance.get(resourceAndInstanceInfo) match { +// case Some(map) ⇒ +// map +// +// case None ⇒ +// val map = mutable.Map[String, Any]() +// chargingDataOfResourceInstance(resourceAndInstanceInfo) = map +// map +// +// } +// } + +// def setChargingDataForResourceEvent( +// resourceAndInstanceInfo: (String, String), +// data: mutable.Map[String, Any] +// ): Unit = { +// chargingDataOfResourceInstance(resourceAndInstanceInfo) = data +// } /** * Find those events from `implicitlyIssuedStartEvents` and `previousResourceEvents` that will generate implicit @@ -195,46 +156,46 @@ final class WorkingUserState( * * @see [[gr.grnet.aquarium.charging.ChargingBehavior]] */ - def findAndRemoveGeneratorsOfImplicitEndEvents( - chargingBehaviorOfResourceType: ResourceType ⇒ ChargingBehavior, - /** - * The `occurredMillis` that will be recorded in the synthetic implicit OFFs. - * Normally, this will be the end of a billing month. - */ - newOccuredMillis: Long - ): (List[ResourceEventModel], List[ResourceEventModel]) = { - - val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]() - val checkSet = mutable.Set[ResourceEventModel]() - - def doItFor(map: mutable.Map[(String, String), ResourceEventModel]): Unit = { - val resourceEvents = map.valuesIterator - for { - resourceEvent ← resourceEvents - resourceType ← resourceTypesMap.get(resourceEvent.safeResource) - chargingBehavior = chargingBehaviorOfResourceType.apply(resourceType) - } { - if(chargingBehavior.supportsImplicitEvents) { - if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) { - val implicitEnd = chargingBehavior.constructImplicitEndEventFor(resourceEvent, newOccuredMillis) - - if(!checkSet.contains(resourceEvent)) { - checkSet.add(resourceEvent) - buffer append ((resourceEvent, implicitEnd)) - } - - // remove it anyway - map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceID)) - } - } - } - } - - doItFor(previousEventOfResourceInstance) // we give priority for previous events - doItFor(implicitlyIssuedStartEventOfResourceInstance) // ... over implicitly issued ones ... - - (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList) - } +// def findAndRemoveGeneratorsOfImplicitEndEvents( +// chargingBehaviorOfResourceType: ResourceType ⇒ ChargingBehavior, +// /** +// * The `occurredMillis` that will be recorded in the synthetic implicit OFFs. +// * Normally, this will be the end of a billing month. +// */ +// newOccuredMillis: Long +// ): (List[ResourceEventModel], List[ResourceEventModel]) = { +// +// val buffer = mutable.ListBuffer[(ResourceEventModel, ResourceEventModel)]() +// val checkSet = mutable.Set[ResourceEventModel]() +// +// def doItFor(map: mutable.Map[(String, String), ResourceEventModel]): Unit = { +// val resourceEvents = map.valuesIterator +// for { +// resourceEvent ← resourceEvents +// resourceType ← resourceTypesMap.get(resourceEvent.safeResource) +// chargingBehavior = chargingBehaviorOfResourceType.apply(resourceType) +// } { +// if(chargingBehavior.supportsImplicitEvents) { +// if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) { +// val implicitEnd = chargingBehavior.constructImplicitEndEventFor(resourceEvent, newOccuredMillis) +// +// if(!checkSet.contains(resourceEvent)) { +// checkSet.add(resourceEvent) +// buffer append ((resourceEvent, implicitEnd)) +// } +// +// // remove it anyway +// map.remove((resourceEvent.safeResource, resourceEvent.safeInstanceID)) +// } +// } +// } +// } +// +// doItFor(previousEventOfResourceInstance) // we give priority for previous events +// doItFor(implicitlyIssuedStartEventOfResourceInstance) // ... over implicitly issued ones ... +// +// (buffer.view.map(_._1).toList, buffer.view.map(_._2).toList) +// } } object WorkingUserState { diff --git a/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala index e9e932f..f962a70 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala @@ -40,7 +40,6 @@ import gr.grnet.aquarium.util.Loggable import gr.grnet.aquarium.logic.accounting.dsl.Timeslot import gr.grnet.aquarium.policy._ import collection.immutable -import gr.grnet.aquarium.policy.ResourceType import gr.grnet.aquarium.policy.EffectiveUnitPrice import gr.grnet.aquarium.charging.Chargeslot @@ -86,18 +85,16 @@ object TimeslotComputations extends Loggable { alignedTimeslot: Timeslot, policy: PolicyModel, agreement: UserAgreementModel, - resourceType: ResourceType, effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable ): SortedMap[Timeslot, Double] = { // Note that most of the code is taken from calcChangeChunks() - val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType, effectivePriceTableSelector) + val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, effectivePriceTableSelector) ret map {case (t,p) => (t,p.unitPrice)} } def computeInitialChargeslots( referenceTimeslot: Timeslot, - resourceType: ResourceType, policyByTimeslot: SortedMap[Timeslot, PolicyModel], agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel], effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable @@ -134,7 +131,6 @@ object TimeslotComputations extends Loggable { alignedTimeslot, policy, userAgreement, - resourceType, effectivePriceTableSelector ) @@ -210,7 +206,6 @@ object TimeslotComputations extends Loggable { alignedTimeslot: Timeslot, policy: PolicyModel, agreement: UserAgreementModel, - resourceType: ResourceType, effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable ): PriceMap = { diff --git a/src/main/scala/gr/grnet/aquarium/connector/handler/IMEventPayloadHandler.scala b/src/main/scala/gr/grnet/aquarium/connector/handler/IMEventPayloadHandler.scala index 59991ae..5aa5583 100644 --- a/src/main/scala/gr/grnet/aquarium/connector/handler/IMEventPayloadHandler.scala +++ b/src/main/scala/gr/grnet/aquarium/connector/handler/IMEventPayloadHandler.scala @@ -41,8 +41,7 @@ import gr.grnet.aquarium.converter.JsonTextFormat import gr.grnet.aquarium.store.{IMEventStore, LocalFSEventStore} import gr.grnet.aquarium.event.model.im.{StdIMEvent, IMEventModel} import gr.grnet.aquarium.actor.message.event.ProcessIMEvent -import gr.grnet.aquarium.util.date.MutableDateCalc -import gr.grnet.aquarium.util.{LogHelpers, Tags, shortClassNameOf} +import gr.grnet.aquarium.util.{LogHelpers, Tags} /** * A [[gr.grnet.aquarium.connector.handler.PayloadHandler]] for diff --git a/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala index f13bf97..e1897fb 100644 --- a/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala +++ b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala @@ -40,14 +40,19 @@ import scala.annotation.tailrec import gr.grnet.aquarium.util.shortNameOfType /** - * A full price table provides detailed pricing information for all resources. + * A full price table provides detailed pricing information for all resource types. * - * @author Christos KK Loverdos + * @param perResource The key is some [[gr.grnet.aquarium.policy.ResourceType]]`.name`. + * The value is a Map from selector to either an [[gr.grnet.aquarium.policy.EffectivePriceTable]] + * or another Map (that designates another level of search path). + * See `policy.json` for samples. + * + *@author Christos KK Loverdos */ case class FullPriceTable( - perResource: Map[String/*Resource*/, - Map[String/*Per-ChargingBehavior Key, "default" is the default*/, Any]] + perResource: Map[String /* The key is some ResourceType.name */, + Map[String /* Use "default" for the simple cases */, Any]] ) { def effectivePriceTableOfSelectorForResource( @@ -55,36 +60,109 @@ case class FullPriceTable( resource: String ): EffectivePriceTable = { + // Most of the code is for exceptional cases, which we identify in detail. @tailrec def find( - selectorPath: List[String], - selectorData: Any + partialSelectorPath: List[String], + partialSelectorData: Any ): EffectivePriceTable = { - selectorPath match { - case Nil ⇒ - // End of selector path. This means that the data must be an EffectivePriceTable - selectorData match { - case ept: EffectivePriceTable ⇒ - ept - - case _ ⇒ - // TODO more informative error message (include selector path, resource?) - throw new AquariumInternalError("Got %s instead of an %s", selectorData, shortNameOfType[EffectivePriceTable]) - } + partialSelectorPath match { + case selector :: Nil ⇒ + // One selector, meaning that the selectorData must be a Map[String, EffectivePriceTable] + partialSelectorData match { + case selectorMap: Map[_,_] ⇒ + // The selectorData is a map indeed + selectorMap.asInstanceOf[Map[String, _]].get(selector) match { + case Some(selected: EffectivePriceTable) ⇒ + // Yes, it is a map of the right type (OK, we assume keys are always Strings) + // (we only check the value type) + selected + + case Some(badSelected) ⇒ + // The selectorData is a map but the value is not of the required type + throw new AquariumInternalError( + "[AQU-SEL-001] Cannot select path %s for resource %s. Found %s instead of an %s at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + badSelected, + shortNameOfType[EffectivePriceTable], + partialSelectorPath.mkString("/") + ) + ) - case key :: tailSelectorPath ⇒ - // Intermediate path. This means we have another round of Map[String, Any] - selectorData match { - case selectorMap: Map[_, _] ⇒ - selectorMap.asInstanceOf[Map[String, _]].get(key) match { case None ⇒ - throw new AquariumInternalError("Did not find value for selector %s", key) + // The selectorData is a map but it does nto contain the selector + throw new AquariumInternalError( + "[AQU-SEL-002] Cannot select path %s for resource %s. Nothing found at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + partialSelectorPath.mkString("/") + ) + ) + } + + + case badData ⇒ + // The selectorData is not a map. So we have just one final selector but no map to select from. + throw new AquariumInternalError( + "[AQU-SEL-003] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + badData, + partialSelectorPath.mkString("/") + ) + ) + } - case Some(nextSelectorData) ⇒ - find(tailSelectorPath, nextSelectorData) + case selector :: selectorTail ⇒ + // More than one selector in the path, meaning that the selectorData must be a Map[String, Map[String, _]] + partialSelectorData match { + case selectorMap: Map[_,_] ⇒ + // The selectorData is a map indeed + selectorMap.asInstanceOf[Map[String,_]].get(selector) match { + case Some(furtherSelectorMap: Map[_,_]) ⇒ + // The selectorData is a map and we found the respective value for the selector to be a map. + find(selectorTail, furtherSelectorMap) + + case Some(furtherBad) ⇒ + // The selectorData is a map but the respective value is not a map, so that + // the selectorTail path cannot be used. + throw new AquariumInternalError( + "[AQU-SEL-004] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + furtherBad, + partialSelectorPath.mkString("/") + ) + ) + + case None ⇒ + // The selectorData is a map but it does not contain the selector + throw new AquariumInternalError( + "[AQU-SEL-005] Cannot select path %s for resource %s. Nothing found at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + partialSelectorPath.mkString("/") + ) + ) } + + case badData ⇒ + // The selectorData is not a Map. So we have more than one selectors but no map to select from. + throw new AquariumInternalError( + "[AQU-SEL-006] Cannot select path %s for resource %s. Found %s instead of a Map at partial selector path %s".format( + selectorPath.mkString("/"), + resource, + badData, + partialSelectorPath.mkString("/") + ) + ) } + + case Nil ⇒ + throw new AquariumInternalError("") + } } diff --git a/src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala b/src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala index 41a5ca8..b3fb84f 100644 --- a/src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala +++ b/src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala @@ -129,10 +129,7 @@ extends StoreProvider model.billingYear, model.billingMonth, model.chargingReason, - model.previousResourceEvents, - model.implicitlyIssuedStartEvents, - model.accumulatingAmountOfResourceInstance, - model.chargingDataOfResourceInstance, + model.stateOfResources, model.billingPeriodOutOfSyncResourceEventsCounter, model.agreementHistory, model.walletEntries diff --git a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBUserState.scala b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBUserState.scala index a0ee96b..3ba34bb 100644 --- a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBUserState.scala +++ b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBUserState.scala @@ -35,9 +35,8 @@ package gr.grnet.aquarium.store.mongodb -import gr.grnet.aquarium.charging.state.{UserStateModelSkeleton, AgreementHistory, UserStateModel} +import gr.grnet.aquarium.charging.state.{ResourcesChargingState, UserStateModelSkeleton, AgreementHistory, UserStateModel} import gr.grnet.aquarium.charging.reason.ChargingReason -import gr.grnet.aquarium.event.model.resource.ResourceEventModel import gr.grnet.aquarium.charging.wallet.WalletEntry import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters} @@ -57,10 +56,7 @@ case class MongoDBUserState( billingYear: Int, billingMonth: Int, chargingReason: ChargingReason, - previousResourceEvents: List[ResourceEventModel], - implicitlyIssuedStartEvents: List[ResourceEventModel], - accumulatingAmountOfResourceInstance: Map[String, Double], - chargingDataOfResourceInstance: Map[String, Map[String, Any]], + stateOfResources: Map[String, ResourcesChargingState], billingPeriodOutOfSyncResourceEventsCounter: Long, agreementHistory: AgreementHistory, walletEntries: List[WalletEntry] @@ -90,10 +86,7 @@ object MongoDBUserState { model.billingYear, model.billingMonth, model.chargingReason, - model.previousResourceEvents, - model.implicitlyIssuedStartEvents, - model.accumulatingAmountOfResourceInstance, - model.chargingDataOfResourceInstance, + model.stateOfResources, model.billingPeriodOutOfSyncResourceEventsCounter, model.agreementHistory, model.walletEntries -- 1.7.10.4