Implement Continuous behavior with the new scheme
authorChristos KK Loverdos <loverdos@gmail.com>
Thu, 23 Aug 2012 13:22:02 +0000 (16:22 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Thu, 23 Aug 2012 13:22:02 +0000 (16:22 +0300)
Fix a bunch of related stuff in the process.

src/main/scala/gr/grnet/aquarium/Aquarium.scala
src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala
src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala
src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala
src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala

index 0e55a09..b80a3d7 100644 (file)
@@ -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 = {
index 6b2446d..7316fc7 100644 (file)
@@ -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.")
   }
 
index 2bd1364..f945fb6 100644 (file)
@@ -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,
index 9a5551e..e4adda6 100644 (file)
 
 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)
   }
 
   /**
index 7d716f6..44819e9 100644 (file)
@@ -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
   }
index 18ff42c..d532812 100644 (file)
 
 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
   }
 }
 
index c1637ac..d62a5ad 100644 (file)
@@ -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,
index a6b70ff..eea5736 100644 (file)
@@ -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)
 
index 7cb207a..094a605 100644 (file)
@@ -79,4 +79,8 @@ final class WorkingResourceInstanceChargingState(
     this.previousValue = this.currentValue
     this.currentValue = value
   }
+
+  def setOnePreviousEvent(event: ResourceEventModel) {
+    this.previousEvents = event :: Nil
+  }
 }
index ed9f56b..71314e4 100644 (file)
 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 <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: _*)
@@ -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
+//    }
+//  }
 }
index e1897fb..d9f3520 100644 (file)
 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)
   }
 }