Fix a bunch of related stuff in the process.
def initialUserBalance(role: String, referenceTimeMillis: Long): Double = {
// FIXME: Where is the mapping?
- 1000.0
+ 0.0
}
def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = {
/* 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.")
}
/**
*
- * @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 processResourceEvent(
aquarium: Aquarium,
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 gr.grnet.aquarium.util._
-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.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
+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.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
)
}
- final protected def ensureWorkingState(
+ final protected def ensureInitializedWorkingState(
workingResourcesChargingState: WorkingResourcesChargingState,
resourceEvent: ResourceEventModel
) {
- ensureResourcesChargingStateDetails(workingResourcesChargingState.details)
- ensureResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
+ ensureInitializedResourcesChargingStateDetails(workingResourcesChargingState.details)
+ ensureInitializedResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
}
- protected def ensureResourcesChargingStateDetails(
- details: mutable.Map[String, Any]
- ) {}
+ protected def ensureInitializedResourcesChargingStateDetails(details: mutable.Map[String, Any]) {}
- protected def ensureResourceInstanceChargingState(
+ protected def ensureInitializedResourceInstanceChargingState(
workingResourcesChargingState: WorkingResourcesChargingState,
resourceEvent: ResourceEventModel
) {
}
}
+ protected def fillWorkingResourceInstanceChargingStateFromEvent(
+ workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
+ resourceEvent: ResourceEventModel
+ ) {
+
+ workingResourceInstanceChargingState.currentValue = resourceEvent.value
+ }
+
protected def computeWalletEntriesForNewEvent(
resourceEvent: ResourceEventModel,
resourceType: ResourceType,
walletEntryRecorder.apply(newWalletEntry)
- (1, newTotalCredits)
+ (1, sumOfCreditsToSubtract)
}
totalCredits
)
- fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
+ fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource, logger)
}
/**
}
val m0 = TimeHelpers.nowMillis()
- val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent(
+ val (walletEntriesCount, creditsToSubtract) = chargingBehavior.processResourceEvent(
aquarium,
resourceEvent,
resourceType,
}
workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis)
- workingUserState.totalCredits = newTotalCredits
+ workingUserState.totalCredits -= creditsToSubtract
true
}
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 gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
+import gr.grnet.aquarium.policy.{FullPriceTable, ResourceType}
+import gr.grnet.aquarium.util.LogHelpers.Debug
import scala.collection.mutable
-import gr.grnet.aquarium.Aquarium
-import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging.wallet.WalletEntry
/**
* In practice a resource usage will be charged for the total amount of usage
val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
- val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
+ val explanation = "Time(%s) * OldTotal(%s) * UnitPrice(%s)".format(
hrs(timeDeltaMillis),
oldAccumulatingAmount,
unitPrice
referenceTimeslot: Timeslot,
totalCredits: Double
): List[String] = {
- Nil
+ List(FullPriceTable.DefaultSelectorKey)
}
def initialChargingDetails: Map[String, Any] = Map()
override def processResourceEvent(
aquarium: Aquarium,
- currentResourceEvent: ResourceEventModel,
+ resourceEvent: ResourceEventModel,
resourceType: ResourceType,
billingMonthInfo: BillingMonthInfo,
workingResourcesChargingState: WorkingResourcesChargingState,
totalCredits: Double,
walletEntryRecorder: WalletEntry ⇒ Unit
): (Int, Double) = {
- (0,0)
+
+ // 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
}
}
): (Double /* credits */, String /* explanation */) = {
val currentValue = workingResourceInstanceChargingState.currentValue
- val credits = currentValue
- val explanation = "Value(%s)".format(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)
}
// 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).
- val instanceID = resourceEvent.instanceID
- val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
-
- // 0. Ensure proper state per resource and per instance
- ensureWorkingState(workingResourcesChargingState, resourceEvent)
-
- // 1. Find the unit price at the moment of the event
+ // 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)
computeWalletEntriesForNewEvent(
resourceEvent,
): (Double /* credits */, String /* explanation */) = {
val credits = hrs(timeDeltaMillis) * unitPrice
- val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
+ val explanation = "Time(%s) * UnitPrice(%s)".format(hrs(timeDeltaMillis), unitPrice)
(credits, explanation)
this.previousValue = this.currentValue
this.currentValue = value
}
+
+ def setOnePreviousEvent(event: ResourceEventModel) {
+ this.previousEvents = event :: Nil
+ }
}
package gr.grnet.aquarium.charging.state
import scala.collection.mutable
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
/**
- * Working (mutable state) for a resource instances of the same resource type.
+ * 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
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
final class WorkingResourcesChargingState(
- val details: mutable.Map[String, Any],
- val stateOfResourceInstance: mutable.Map[String, WorkingResourceInstanceChargingState]
+ val details: mutable.Map[String /* any string */, Any],
+ val stateOfResourceInstance: mutable.Map[String /* InstanceID */, WorkingResourceInstanceChargingState]
) {
def immutableDetails = Map(this.details.toSeq: _*)
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
+// }
+// }
}
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 resource types.
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.
partialSelectorData: Any
): 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]
}
case Nil ⇒
- throw new AquariumInternalError("")
+ 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)
}
}