}
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 ⇒
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
/**
*
*/
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)
}
}
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
}
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)
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 <loverdos@gmail.com>
*/
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
}
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
*/
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,
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
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)
}
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
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)
(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)
* 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) {
Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
resourceType,
userAgreements.agreementByTimeslot,
- this.getResourceInstanceUndefinedAmount,
+ 0.0,
totalCredits,
aquarium.policyStore,
walletEntryRecorder
}
}
- 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",
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
}
+++ /dev/null
-/*
- * 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 <loverdos@gmail.com>
- */
-
-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 <loverdos@gmail.com>
- */
-case object ChargingBehaviorNameInput extends ChargingInput("chargingBehaviorName")
-
-/**
- * The type of [[gr.grnet.aquarium.charging.ChargingInput]] that holds the total credits.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-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 <loverdos@gmail.com>
- */
-case object UnitPriceInput extends ChargingInput("unitPrice")
-
-case object DetailsInput extends ChargingInput("details")
-
-
-
-
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}
/**
lazy val resourceEventStore = aquarium.resourceEventStore
//+ Lifecycle
- def start() = ()
+ def start() {}
- def stop() = ()
+ def stop() {}
//- Lifecycle
true,
billingMonthInfo.year,
billingMonthInfo.month,
- None
+ ""
)
// We always save the state when it is a full month billing
}
}
/**
- * Processes one resource event and computes relevant charges.
+ * Processes one resource event and computes relevant, incremental charges.
*
* @param resourceEvent
* @param workingUserState
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(
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(
workingUserState.walletEntries ++= specialWorkingUserState.walletEntries
workingUserState.totalCredits = specialWorkingUserState.totalCredits
- }
+ }*/
workingUserState
}
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
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-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 {
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.
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-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
}
}
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 <loverdos@gmail.com>
*/
-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
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
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-case class AgreementHistory(agreements: List[UserAgreementModel]) {
+case class AgreementHistory(agreements: List[UserAgreementModel]) extends AgreementHistoryModel {
def toWorkingAgreementHistory = {
(new WorkingAgreementHistory) ++ agreements
}
* 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 <loverdos@gmail.com>
*/
+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]
}
--- /dev/null
+/*
+ * 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 <loverdos@gmail.com>
+ */
+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
+ )
+ }
+}
* 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 <loverdos@gmail.com>
*/
+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
}
* 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 <loverdos@gmail.com>
*/
-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
+ )
+ }
+}
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
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]
bmi.year,
bmi.month,
chargingReason,
- Nil,
- Nil,
- Map(),
Map(),
0L,
AgreementHistory.initial(initialAgreement),
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
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
*/
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 = {
this.parentIDInStore,
this.chargingReason,
resourceTypesMap,
- mutablePreviousEventsMap,
- mutableImplicitlyIssuedStartMap,
- mutableAccumulatingAmountMap,
- mutableChargingDataMap,
+ mutableStateOfChargingBehavior,
this.totalCredits,
mutableAgreementHistory,
this.occurredMillis,
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
/**
*
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
--- /dev/null
+/*
+ * 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 <loverdos@gmail.com>
+ */
+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
+ }
+}
--- /dev/null
+/*
+ * 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 <loverdos@gmail.com>
+ */
+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
+ )
+ }
+}
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
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 = {
}
}
- 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,
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
*
* @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 {
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
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
alignedTimeslot,
policy,
userAgreement,
- resourceType,
effectivePriceTableSelector
)
alignedTimeslot: Timeslot,
policy: PolicyModel,
agreement: UserAgreementModel,
- resourceType: ResourceType,
effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
): PriceMap = {
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
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 <loverdos@gmail.com>
+ * @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 <loverdos@gmail.com>
*/
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(
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("")
+
}
}
model.billingYear,
model.billingMonth,
model.chargingReason,
- model.previousResourceEvents,
- model.implicitlyIssuedStartEvents,
- model.accumulatingAmountOfResourceInstance,
- model.chargingDataOfResourceInstance,
+ model.stateOfResources,
model.billingPeriodOutOfSyncResourceEventsCounter,
model.agreementHistory,
model.walletEntries
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}
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]
model.billingYear,
model.billingMonth,
model.chargingReason,
- model.previousResourceEvents,
- model.implicitlyIssuedStartEvents,
- model.accumulatingAmountOfResourceInstance,
- model.chargingDataOfResourceInstance,
+ model.stateOfResources,
model.billingPeriodOutOfSyncResourceEventsCounter,
model.agreementHistory,
model.walletEntries