<preparationGoals>clean verify</preparationGoals>
</configuration>
</plugin>
+
+ <!--<plugin>-->
+ <!--<groupId>org.apache.maven.plugins</groupId>-->
+ <!--<artifactId>maven-shade-plugin</artifactId>-->
+ <!--<version>1.7.1</version>-->
+ <!--<configuration>-->
+ <!--<!– put your configurations here –>-->
+ <!--</configuration>-->
+ <!--<executions>-->
+ <!--<execution>-->
+ <!--<phase>package</phase>-->
+ <!--<goals>-->
+ <!--<goal>shade</goal>-->
+ <!--</goals>-->
+ <!--</execution>-->
+ <!--</executions>-->
+ <!--</plugin>-->
</plugins>
</build>
</project>
# Queue declarations for receiving IM events, format is "exchange:routing.key:queue"
rabbitmq.imevents.queues=astakos:astakos.user:aquarium-imevents
+# For sending credit modifications
+rabbitmq.imevents.credit=astakos:astakos-events-credit
+
# REST service listening port
rest.port=8888
0,
Long.MaxValue,
role,
- PolicyDefinedFullPriceTableRef
+ PolicyDefinedFullPriceTableRef()
)
}
def initialUserBalance(role: String, referenceTimeMillis: Long): Double = {
// FIXME: Where is the mapping?
- 1000.0
+ 0.0
}
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.reason.{InitialUserActorSetup, RealtimeChargingReason}
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement}
+import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel}
+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.{AbstractBillEntry, BillEntry}
effectiveFromMillis,
Long.MaxValue,
role,
- PolicyDefinedFullPriceTableRef
+ PolicyDefinedFullPriceTableRef()
)
this._workingAgreementHistory += newAgreement
now,
this._userStateBootstrap,
aquarium.currentResourceTypesMap,
- InitialUserActorSetup(),
aquarium.userStateStore.insertUserState
)
/* Convert astakos message for adding credits
to a regular RESOURCE message */
def onHandleAddCreditsEvent(imEvent : IMEventModel) = {
+ DEBUG("Got %s", imEvent.toJsonString)
+
val credits = imEvent.details(IMEventModel.DetailsNames.credits).toInt.toDouble
val event = new StdResourceEvent(
imEvent.id,
imEvent.eventVersion,
imEvent.details
)
- //Console.err.println("Event: " + event)
- //Console.err.println("Total credits before: " + _workingUserState.totalCredits)
+ DEBUG("Transformed to %s", event)
+ DEBUG("Total credits before: %s", _workingUserState.totalCredits)
+ aquarium.resourceEventStore.insertResourceEvent(event)
onProcessResourceEvent(new ProcessResourceEvent(event))
- //Console.err.println("Total credits after: " + _workingUserState.totalCredits)
+ DEBUG("Total credits after: %s", _workingUserState.totalCredits)
//Console.err.println("OK.")
}
}
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)
val nowBillingMonthInfo = BillingMonthInfo.fromMillis(now)
val nowYear = nowBillingMonthInfo.year
now max eventOccurredMillis,
this._userStateBootstrap,
currentResourcesMap,
- chargingReason,
stdUserStateStoreFunc
)
chargingService.processResourceEvent(
rcEvent,
this._workingUserState,
- chargingReason,
nowBillingMonthInfo,
true
)
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
+ * @return The number of wallet entries recorded and the credit difference generated during processing (these are
+ * the credits to subtract from the 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
}
package gr.grnet.aquarium.charging
-import scala.collection.immutable
-import scala.collection.mutable
-
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
-import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
-import com.ckkloverdos.key.TypedKey
-import gr.grnet.aquarium.util._
-import gr.grnet.aquarium.util.LogHelpers.Debug
-import gr.grnet.aquarium.util.date.TimeHelpers
+import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
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.event.model.resource.ResourceEventModel
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
import gr.grnet.aquarium.store.PolicyStore
-import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
+import gr.grnet.aquarium.util._
+import gr.grnet.aquarium.util.date.TimeHelpers
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
+import scala.collection.immutable
+import scala.collection.mutable
+
/**
* 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 ensureInitializedWorkingState(
+ workingResourcesChargingState: WorkingResourcesChargingState,
+ resourceEvent: ResourceEventModel
+ ) {
+ ensureInitializedResourcesChargingStateDetails(workingResourcesChargingState.details)
+ ensureInitializedResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
+ }
+
+ protected def ensureInitializedResourcesChargingStateDetails(details: mutable.Map[String, Any]) {}
+
+ protected def ensureInitializedResourceInstanceChargingState(
+ workingResourcesChargingState: WorkingResourcesChargingState,
+ resourceEvent: ResourceEventModel
+ ) {
+
+ val instanceID = resourceEvent.instanceID
+ val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
+
+ stateOfResourceInstance.get(instanceID) match {
+ case None ⇒
+ stateOfResourceInstance(instanceID) = newWorkingResourceInstanceChargingState()
+
+ case _ ⇒
+ }
+ }
+
+ protected def fillWorkingResourceInstanceChargingStateFromEvent(
+ workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+ resourceEvent: ResourceEventModel
+ ) {
+
+ workingResourceInstanceChargingState.currentValue = resourceEvent.value
+ }
+
+ 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)
walletEntryRecorder.apply(newWalletEntry)
- (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]]
+ (1, sumOfCreditsToSubtract)
}
- 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)
+ fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource, logger)
}
/**
* 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
+ 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)
- }
-
- 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
billingMonthInfo: BillingMonthInfo,
userStateBootstrap: UserStateBootstrap,
defaultResourceTypesMap: Map[String, ResourceType],
- chargingReason: ChargingReason,
userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
userStateBootstrap,
billingMonthInfo,
defaultResourceTypesMap,
- chargingReason,
userStateRecorder
)
- val newChargingReason = MonthlyBillChargingReason(chargingReason, billingMonthInfo)
- workingUserState.chargingReason = newChargingReason
val monthlyUserState0 = workingUserState.toUserState(
true,
billingMonthInfo.year,
billingMonthInfo.month,
- None
+ ""
)
// We always save the state when it is a full month billing
// First ask if it exists and compute only if not
val initialUserState0 = StdUserState.createInitialUserStateFromBootstrap(
userStateBootstrap,
- TimeHelpers.nowMillis(),
- InitialUserStateSetup(Some(chargingReason)) // we record the originating calculation reason
+ TimeHelpers.nowMillis()
)
Debug(logger, "Created (from bootstrap) initial user state %s", initialUserState0)
// ZERO, we are OK!
case 0 ⇒
// NOTE: Keep the caller's calculation reason
- val userStateModel = latestUserState.newWithChargingReason(chargingReason)
- userStateModel.toWorkingUserState(defaultResourceTypesMap)
+ latestUserState.toWorkingUserState(defaultResourceTypesMap)
// We had more, so must recompute
case n if n > 0 ⇒
}
}
/**
- * Processes one resource event and computes relevant charges.
+ * Processes one resource event and computes relevant, incremental charges.
*
* @param resourceEvent
* @param workingUserState
- * @param chargingReason
* @param billingMonthInfo
*/
def processResourceEvent(
resourceEvent: ResourceEventModel,
workingUserState: 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, creditsToSubtract) = 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
+ workingUserState.totalCredits -= creditsToSubtract
+
+ true
}
def processResourceEvents(
resourceEvents: Traversable[ResourceEventModel],
workingUserState: WorkingUserState,
- chargingReason: ChargingReason,
billingMonthInfo: BillingMonthInfo
): Unit = {
processResourceEvent(
currentResourceEvent,
workingUserState,
- chargingReason,
billingMonthInfo,
false
)
userStateBootstrap: UserStateBootstrap,
billingMonthInfo: BillingMonthInfo,
defaultResourceTypesMap: Map[String, ResourceType],
- chargingReason: ChargingReason,
userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
billingMonthInfo.monthStopMillis,
userStateBootstrap,
defaultResourceTypesMap,
- chargingReason,
userStateRecorder
)
}
* @param billingEndTimeMillis Bill from start of month up to (and including) this time.
* @param userStateBootstrap
* @param resourceTypesMap
- * @param chargingReason
* @param userStateRecorder
* @return
*/
billingEndTimeMillis: Long,
userStateBootstrap: UserStateBootstrap,
resourceTypesMap: Map[String, ResourceType],
- chargingReason: ChargingReason,
userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
val isFullMonthBilling = billingEndTimeMillis == billingMonthInfo.monthStopMillis
val userID = userStateBootstrap.userID
- Debug(logger, "%s", chargingReason)
-
// In order to replay the full month, we start with the state at the beginning of the month.
val previousBillingMonthInfo = billingMonthInfo.previousMonth
val workingUserState = findOrCalculateWorkingUserStateAtEndOfBillingMonth(
previousBillingMonthInfo,
userStateBootstrap,
resourceTypesMap,
- chargingReason,
userStateRecorder
)
processResourceEvent(
currentResourceEvent,
workingUserState,
- chargingReason,
billingMonthInfo,
false
)
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.Aquarium
+import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
+import gr.grnet.aquarium.charging.wallet.WalletEntry
+import gr.grnet.aquarium.computation.BillingMonthInfo
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.{FullPriceTable, ResourceType}
+import gr.grnet.aquarium.util.LogHelpers.Debug
+import scala.collection.mutable
/**
* 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) * UnitPrice(%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] = {
+ List(FullPriceTable.DefaultSelectorKey)
}
- def mustGenerateDummyFirstEvent = true
+ def initialChargingDetails: Map[String, Any] = Map()
- def supportsImplicitEvents = {
- true
- }
-
- 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,
+ resourceEvent: ResourceEventModel,
+ resourceType: ResourceType,
+ billingMonthInfo: BillingMonthInfo,
+ workingResourcesChargingState: WorkingResourcesChargingState,
+ userAgreements: AgreementHistoryModel,
+ totalCredits: Double,
+ walletEntryRecorder: WalletEntry ⇒ Unit
+ ): (Int, Double) = {
+
+ // 1. Ensure proper initial state per resource and per instance
+ ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
+
+ // 2. Fill in data from the new event
+ val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
+ val workingResourcesChargingStateDetails = workingResourcesChargingState.details
+ val instanceID = resourceEvent.instanceID
+ val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
+ fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
+
+ val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match {
+ case Some(previousEvent) ⇒
+ Debug(logger, "I have previous event %s", previousEvent.toDebugString)
+ previousEvent
+
+
+ case None ⇒
+ // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
+ // Let's see if we can create a dummy previous event.
+ Debug(logger, "First event of its kind %s", resourceEvent.toDebugString)
+
+ val dummyFirstEventDetails = Map(
+ ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
+ ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
+ ResourceEventModel.Names.details_aquarium_reference_event_id -> resourceEvent.id,
+ ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> resourceEvent.stringIDInStoreOrEmpty
+ )
+
+ val dummyFirstEventValue = 0.0 // TODO From configuration
+ val dummyFirstEvent = resourceEvent.withDetailsAndValue(
+ dummyFirstEventDetails,
+ dummyFirstEventValue,
+ billingMonthInfo.monthStartMillis // TODO max(billingMonthInfo.monthStartMillis, userAgreementModel.validFromMillis)
+ )
+
+ Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString)
+
+ dummyFirstEvent
+ }
+
+ val retval = computeWalletEntriesForNewEvent(
+ resourceEvent,
+ resourceType,
+ billingMonthInfo,
+ totalCredits,
+ Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis),
+ userAgreements.agreementByTimeslot,
+ workingResourcesChargingStateDetails,
+ workingResourceInstanceChargingState,
+ aquarium.policyStore,
+ walletEntryRecorder
+ )
+
+ // We need just one previous event, so we update it
+ workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent)
+
+ retval
+ }
}
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
+ // Always remember to multiply with the `unitPrice`, since it scales the credits, depending on
+ // the particular resource type tha applies.
+ val credits = currentValue * unitPrice
+ val explanation = "Value(%s) * UnitPrice(%s)".format(currentValue, unitPrice)
+
+ (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
- }
+ // 1. Ensure proper initial state per resource and per instance
+ ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
- def getResourceInstanceInitialAmount = 0.0
+ // 2. Fill in data from the new event
+ val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
+ val workingResourcesChargingStateDetails = workingResourcesChargingState.details
+ val instanceID = resourceEvent.instanceID
+ val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
+ fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
- def supportsImplicitEvents = false
+ 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) * UnitPrice(%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
+++ /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.reason
-
-import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.event.model.im.IMEventModel
-import gr.grnet.aquarium.util.shortClassNameOf
-
-/**
- * Provides information explaining the reason Aquarium calculated a new
- * [[gr.grnet.aquarium.charging.state.UserStateModel]].
- */
-case class ChargingReason(
- details: Map[String, Any],
- billingMonthInfo: Option[BillingMonthInfo],
- parentReason: Option[ChargingReason]
-) {
-
- require(
- details.contains(ChargingReason.Names.name),
- "No name present in the details of %s".format(shortClassNameOf(this))
- )
-
- private[this] def booleanFromDetails(name: String, default: Boolean) = {
- details.get(name) match {
- case Some(value) ⇒
- value.toString.toBoolean
-
- case _ ⇒
- false
- }
- }
-
- def calculateCreditsForImplicitlyTerminated: Boolean =
- booleanFromDetails(ChargingReason.Names.calculateCreditsForImplicitlyTerminated, false)
-
- def forBillingMonthInfo(bmi: BillingMonthInfo) = {
- copy(
- parentReason = Some(this),
- billingMonthInfo = Some(bmi)
- )
- }
-
- def name: String = {
- // This must be always present
- details.get(ChargingReason.Names.name).map(_.toString).getOrElse("<unknown>")
- }
-}
-
-object ChargingReason {
-
- object Names {
- final val name = "name"
-
- final val imEvent = "imEvent"
- final val forWhenMillis = "forWhenMillis"
-
- final val calculateCreditsForImplicitlyTerminated = "calculateCreditsForImplicitlyTerminated"
- }
-
-}
-
-/**
- * Used when the very first user state is saved.
- */
-object InitialUserStateSetup {
- def name = "InitialUserStateSetup"
-
- /**
- * When the user state is initially set up.
- */
- def apply(parentReason: Option[ChargingReason]) = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name
- ),
- None,
- parentReason
- )
- }
-}
-
-object InitialUserActorSetup {
- def name = "InitialUserActorSetup"
-
- /**
- * When the user processing unit (actor) is initially set up.
- */
- def apply() = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name
- ),
- None,
- None
- )
- }
-}
-
-object NoSpecificChargingReason {
- def name = "NoSpecificChargingReason"
-
- /**
- * A calculation made for no specific reason. Can be for testing, for example.
- */
- def apply() = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name
- ),
- None,
- None
- )
- }
-}
-
-object MonthlyBillChargingReason {
- def name = "MonthlyBillChargingReason"
-
- /**
- * An authoritative calculation for the billing period.
- */
- def apply(parentReason: ChargingReason, billingMongthInfo: BillingMonthInfo) = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name,
- ChargingReason.Names.calculateCreditsForImplicitlyTerminated -> true.toString
- ),
- Some(billingMongthInfo),
- Some(parentReason)
- )
- }
-}
-
-object RealtimeChargingReason {
- def name = "RealtimeChargingReason"
-
- /**
- * Used for the real-time billing calculation.
- */
- def apply(parentReason: Option[ChargingReason], forWhenMillis: Long) = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name,
- ChargingReason.Names.forWhenMillis -> forWhenMillis.toString
- ),
- None,
- parentReason
- )
- }
-}
-
-object IMEventArrival {
- def name = "IMEventArrival"
-
- def apply(imEvent: IMEventModel) = {
- ChargingReason(
- Map(
- ChargingReason.Names.name -> name,
- ChargingReason.Names.imEvent -> imEvent.toJsonString
- ),
- None,
- None
- )
- }
-}
* @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
}
--- /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
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+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
import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.converter.{StdConverters, JsonTextFormat}
/**
*
isFullBillingMonth: Boolean,
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]
) extends UserStateModelSkeleton {
-
- def newWithChargingReason(newChargingReason: ChargingReason): StdUserState = {
- this.copy(chargingReason = newChargingReason)
- }
}
final object StdUserState {
final val ResourceInstanceSeparator = "<:/:>"
final val ResourceInstanceSeparatorLength = ResourceInstanceSeparator.length
+ final def fromJsonTextFormat(jsonTextFormat: JsonTextFormat): StdUserState = {
+ StdConverters.AllConverters.convertEx[StdUserState](jsonTextFormat)
+ }
+
+ final def fromJsonString(json: String): StdUserState = {
+ fromJsonTextFormat(JsonTextFormat(json))
+ }
+
final def stringOfResourceAndInstanceID(resource: String, instanceID: String): String = {
def check(key: String, value: String) = {
if(value.indexOf(ResourceInstanceSeparator) != -1) {
userCreationMillis: Long,
occurredMillis: Long,
totalCredits: Double,
- initialAgreement: UserAgreementModel,
- chargingReason: ChargingReason = InitialUserStateSetup(None)
+ initialAgreement: UserAgreementModel
): StdUserState = {
val bmi = BillingMonthInfo.fromMillis(occurredMillis)
false,
bmi.year,
bmi.month,
- chargingReason,
- Nil,
- Nil,
- Map(),
Map(),
0L,
AgreementHistory.initial(initialAgreement),
def createInitialUserStateFromBootstrap(
usb: UserStateBootstrap,
- occurredMillis: Long,
- chargingReason: ChargingReason
+ occurredMillis: Long
): StdUserState = {
createInitialUserState(
usb.userCreationMillis,
occurredMillis,
usb.initialCredits,
- usb.initialAgreement,
- chargingReason
+ usb.initialAgreement
)
}
}
package gr.grnet.aquarium.charging.state
-import gr.grnet.aquarium.util.json.JsonSupport
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
import gr.grnet.aquarium.charging.wallet.WalletEntry
-import gr.grnet.aquarium.charging.reason.ChargingReason
import gr.grnet.aquarium.policy.ResourceType
+import gr.grnet.aquarium.util.json.JsonSupport
/**
*
def billingMonth: Int
- 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
def walletEntries: List[WalletEntry]
def toWorkingUserState(resourceTypesMap: Map[String, ResourceType]): WorkingUserState
-
- def newWithChargingReason(changeReason: ChargingReason): UserStateModel
}
object UserStateModel {
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 = {
new WorkingUserState(
this.userID,
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
def agreementInEffectWhen(whenMillis: Long): Option[UserAgreementModel] = {
agreements.to(
- StdUserAgreement("", None, whenMillis, Long.MaxValue, "", PolicyDefinedFullPriceTableRef)
+ StdUserAgreement("", None, whenMillis, Long.MaxValue, "", PolicyDefinedFullPriceTableRef())
).lastOption
}
--- /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
+ }
+
+ def setOnePreviousEvent(event: ResourceEventModel) {
+ this.previousEvents = event :: Nil
+ }
+}
--- /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) for 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 string */, Any],
+ val stateOfResourceInstance: mutable.Map[String /* InstanceID */, 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
+ )
+ }
+
+ /**
+ * Find the most recent (latest) holder of a resource event.
+ */
+// def findResourceInstanceOfLatestEvent: Option[WorkingResourceInstanceChargingState] = {
+// stateOfResourceInstance.values.toArray.sortWith { (a, b) ⇒
+// (a.previousEvents, b.previousEvents
+// }
+// }
+}
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
import gr.grnet.aquarium.charging.wallet.WalletEntry
/**
final class WorkingUserState(
val userID: String,
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,
isFullBillingMonth,
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.{makeString, UTF_8_Charset}
import java.nio.charset.Charset
-import gr.grnet.aquarium.policy.{ResourceType, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, StdPolicy}
-import gr.grnet.aquarium.charging.state.WorkingUserState
+import gr.grnet.aquarium.policy.{AdHocFullPriceTableRef, PolicyDefinedFullPriceTableRef, FullPriceTableRef, ResourceType, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, StdPolicy}
+import gr.grnet.aquarium.charging.state.{StdUserState, ResourceInstanceChargingState, ResourcesChargingState, WorkingUserState}
+import gr.grnet.aquarium.computation.BillingMonthInfo
/**
* Provides conversion methods from and to JSON.
// implicit final val Formats = (DefaultFormats ++ JodaTimeSerializers.all)
final val StdPolicyFormats = Serialization.formats(FullTypeHints(List(
// gather here all the "difficult" classes
+// classOf[AnyRef]
// [[PolicyModel]]
classOf[StdPolicy],
classOf[EffectivePriceTable],
classOf[EffectiveUnitPrice],
+ // [[UserStateModel]]
+ classOf[StdUserState],
+ classOf[BillingMonthInfo],
+ classOf[ResourcesChargingState],
+ classOf[ResourceInstanceChargingState],
+
// [[WorkingUserState]]
- classOf[WorkingUserState]
+ classOf[WorkingUserState],
+
+ classOf[FullPriceTableRef],
+ classOf[PolicyDefinedFullPriceTableRef],
+ classOf[AdHocFullPriceTableRef]
)))
final val JodaFormats = JodaTimeSerializers.all
-// implicit final val Formats = (DefaultFormats.withHints(FullTypeHints(List(classOf[AnyRef]))) ++ JodaTimeSerializers.all)
- implicit final val Formats: Formats = StdPolicyFormats ++ JodaFormats
+ implicit final val Formats = (DefaultFormats.withHints(FullTypeHints(List(classOf[AnyRef]))) ++ JodaTimeSerializers.all)
+// implicit final val Formats: Formats = StdPolicyFormats ++ JodaFormats
// Serialization.formats(FullTypeHints(List(classOf[AnyRef])))
// final val PolicyModelSerializer: Serializer[PolicyModel] = new Serializer[PolicyModel] {
// def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, _root_.net.liftweb.json.JValue), PolicyModel] = {
package gr.grnet.aquarium.policy
import gr.grnet.aquarium.AquariumInternalError
-import scala.annotation.tailrec
import gr.grnet.aquarium.util.shortNameOfType
+import gr.grnet.aquarium.util.LogHelpers.Debug
+import org.slf4j.Logger
+import scala.annotation.tailrec
/**
- * A full price table provides detailed pricing information for all resources.
+ * A full price table provides detailed pricing information for all resource types.
+ *
+ * @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>
+ *@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(
selectorPath: List[String],
- resource: String
+ resource: String,
+ logger: Logger
): 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])
- }
+ Debug(logger, "find: ")
+ Debug(logger, " partialSelectorPath = %s", partialSelectorPath.mkString("/"))
+ Debug(logger, " partialSelectorData = %s", partialSelectorData)
+
+ 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 Some(nextSelectorData) ⇒
- find(tailSelectorPath, nextSelectorData)
+
+ 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 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(
+ "[AQU-SEL-007] No selector path for resource %s".format(resource)
+ )
+
}
}
+ Debug(logger, "effectivePriceTableOfSelectorForResource:")
+ Debug(logger, " selectorPath = %s", selectorPath.mkString("/"))
+
val selectorDataOpt = perResource.get(resource)
if(selectorDataOpt.isEmpty) {
throw new AquariumInternalError("Unknown resource type '%s'", resource)
}
val selectorData = selectorDataOpt.get
+ Debug(logger, " selectorData = %s", selectorData)
find(selectorPath, selectorData)
}
}
* Refers to an existing full price table that belongs to the given role. The role is implied from the user agreement
* that contains this instance and must be used to retrieve the exact full price table from the Aquarium policy.
*/
-case object PolicyDefinedFullPriceTableRef extends FullPriceTableRef {
+case class PolicyDefinedFullPriceTableRef() extends FullPriceTableRef {
def isAdHoc: Boolean = false
}
def computeFullPriceTable(policy: PolicyModel): FullPriceTable = {
this.fullPriceTableRef match {
- case PolicyDefinedFullPriceTableRef ⇒
+ case PolicyDefinedFullPriceTableRef() ⇒
policy.roleMapping.get(role) match {
case Some(fullPriceTable) ⇒
fullPriceTable
model.isFullBillingMonth,
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.reason.ChargingReason
-import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.charging.state.{ResourcesChargingState, UserStateModelSkeleton, AgreementHistory, UserStateModel}
import gr.grnet.aquarium.charging.wallet.WalletEntry
import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
isFullBillingMonth: Boolean,
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]
) extends UserStateModelSkeleton {
def id = _id
-
- def newWithChargingReason(newChargingReason: ChargingReason): MongoDBUserState = {
- this.copy(chargingReason = newChargingReason)
- }
}
object MongoDBUserState {
model.isFullBillingMonth,
model.billingYear,
model.billingMonth,
- model.chargingReason,
- model.previousResourceEvents,
- model.implicitlyIssuedStartEvents,
- model.accumulatingAmountOfResourceInstance,
- model.chargingDataOfResourceInstance,
+ model.stateOfResources,
model.billingPeriodOutOfSyncResourceEventsCounter,
model.agreementHistory,
model.walletEntries
# Queue declarations for receiving IM events, format is "exchange:routing.key:queue"
rabbitmq.imevents.queues=astakos:astakos.user:aquarium-imevents
+# For sending credit modifications
+rabbitmq.imevents.credit=astakos:astakos-events-credit
+
# REST service listening port
rest.port=8888
* 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 org.junit.Test
+import gr.grnet.aquarium.util.date.TimeHelpers
+import gr.grnet.aquarium.computation.BillingMonthInfo
/**
- * An charging algorithm in executable form.
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-trait ExecutableChargingBehaviorAlgorithm extends (Map[ChargingInput, Any] ⇒ Double)
+class StdUserStateTest {
+ @Test
+ def testJson() {
+ val now = TimeHelpers.nowMillis()
+ val bmi = BillingMonthInfo.fromMillis(now)
+ val state = StdUserState(
+ "id-1", None, "user@grnet.gr",
+ now, 0, 1000.0, false,
+ bmi.year, bmi.month,
+ Map(),
+ 0L,
+ AgreementHistory.Empty,
+ Nil
+ )
+
+ val json = state.toJsonString
+ println(json)
+ val obj = StdUserState.fromJsonString(json)
+
+ assert(state == obj)
+ }
+}