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
}