From: Christos KK Loverdos Date: Thu, 23 Aug 2012 13:22:02 +0000 (+0300) Subject: Implement Continuous behavior with the new scheme X-Git-Url: https://code.grnet.gr/git/aquarium/commitdiff_plain/10c878190c3c2f60e11493d94a5ba68750b1cb3e Implement Continuous behavior with the new scheme Fix a bunch of related stuff in the process. --- diff --git a/src/main/scala/gr/grnet/aquarium/Aquarium.scala b/src/main/scala/gr/grnet/aquarium/Aquarium.scala index 0e55a09..b80a3d7 100644 --- a/src/main/scala/gr/grnet/aquarium/Aquarium.scala +++ b/src/main/scala/gr/grnet/aquarium/Aquarium.scala @@ -290,7 +290,7 @@ final class Aquarium(env: Env) extends Lifecycle with Loggable { def initialUserBalance(role: String, referenceTimeMillis: Long): Double = { // FIXME: Where is the mapping? - 1000.0 + 0.0 } def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = { diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala index 6b2446d..7316fc7 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala @@ -312,6 +312,8 @@ class UserActor extends ReflectiveRoleableActor { /* 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, @@ -325,10 +327,11 @@ class UserActor extends ReflectiveRoleableActor { 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.") } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala index 2bd1364..f945fb6 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala @@ -79,7 +79,8 @@ trait ChargingBehavior { /** * - * @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, diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala index 9a5551e..e4adda6 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala @@ -35,19 +35,19 @@ 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 @@ -82,19 +82,17 @@ abstract class ChargingBehaviorSkeleton( ) } - 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 ) { @@ -110,6 +108,14 @@ abstract class ChargingBehaviorSkeleton( } } + protected def fillWorkingResourceInstanceChargingStateFromEvent( + workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, + resourceEvent: ResourceEventModel + ) { + + workingResourceInstanceChargingState.currentValue = resourceEvent.value + } + protected def computeWalletEntriesForNewEvent( resourceEvent: ResourceEventModel, resourceType: ResourceType, @@ -196,7 +202,7 @@ abstract class ChargingBehaviorSkeleton( walletEntryRecorder.apply(newWalletEntry) - (1, newTotalCredits) + (1, sumOfCreditsToSubtract) } @@ -217,7 +223,7 @@ abstract class ChargingBehaviorSkeleton( totalCredits ) - fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource) + fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource, logger) } /** diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala index 7d716f6..44819e9 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala @@ -206,7 +206,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo } val m0 = TimeHelpers.nowMillis() - val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent( + val (walletEntriesCount, creditsToSubtract) = chargingBehavior.processResourceEvent( aquarium, resourceEvent, resourceType, @@ -223,7 +223,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo } workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis) - workingUserState.totalCredits = newTotalCredits + workingUserState.totalCredits -= creditsToSubtract true } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala index 18ff42c..d532812 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala @@ -35,14 +35,15 @@ 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 @@ -63,7 +64,7 @@ final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) { 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 @@ -80,7 +81,7 @@ final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) { referenceTimeslot: Timeslot, totalCredits: Double ): List[String] = { - Nil + List(FullPriceTable.DefaultSelectorKey) } def initialChargingDetails: Map[String, Any] = Map() @@ -102,7 +103,7 @@ final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) { override def processResourceEvent( aquarium: Aquarium, - currentResourceEvent: ResourceEventModel, + resourceEvent: ResourceEventModel, resourceType: ResourceType, billingMonthInfo: BillingMonthInfo, workingResourcesChargingState: WorkingResourcesChargingState, @@ -110,7 +111,64 @@ final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) { 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 } } diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala index c1637ac..d62a5ad 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala @@ -60,8 +60,10 @@ final class OnceChargingBehavior extends ChargingBehaviorSkeleton(Nil) { ): (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) } @@ -90,16 +92,15 @@ final class OnceChargingBehavior extends ChargingBehaviorSkeleton(Nil) { // 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, diff --git a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala index a6b70ff..eea5736 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala @@ -60,7 +60,7 @@ final class VMChargingBehavior extends ChargingBehaviorSkeleton(List(PowerStatus ): (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) diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala index 7cb207a..094a605 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala @@ -79,4 +79,8 @@ final class WorkingResourceInstanceChargingState( this.previousValue = this.currentValue this.currentValue = value } + + def setOnePreviousEvent(event: ResourceEventModel) { + this.previousEvents = event :: Nil + } } diff --git a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala index ed9f56b..71314e4 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala @@ -36,9 +36,10 @@ 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 @@ -47,8 +48,8 @@ import scala.collection.mutable * @author Christos KK Loverdos */ 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: _*) @@ -64,4 +65,13 @@ final class WorkingResourcesChargingState( 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 +// } +// } } diff --git a/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala index e1897fb..d9f3520 100644 --- a/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala +++ b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala @@ -36,8 +36,10 @@ 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. @@ -57,7 +59,8 @@ case class FullPriceTable( 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. @@ -67,6 +70,10 @@ case class FullPriceTable( 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] @@ -161,17 +168,23 @@ case class FullPriceTable( } 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) } }