WIP charging behavior
authorChristos KK Loverdos <loverdos@gmail.com>
Fri, 17 Aug 2012 13:37:40 +0000 (16:37 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Fri, 17 Aug 2012 13:37:40 +0000 (16:37 +0300)
17 files changed:
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/ChargingBehaviorAliases.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala [new file with mode: 0644]
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/OnOffPolicyResourceState.scala [deleted file]
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/reason/ChargingReason.scala
src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala
src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala
src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala
src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala [deleted file]
src/main/scala/gr/grnet/aquarium/util/LogHelpers.scala
src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala
src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala [deleted file]

index 65d14ff..b31ea37 100644 (file)
@@ -232,8 +232,7 @@ class UserActor extends ReflectiveRoleableActor {
       this._userStateBootstrap,
       aquarium.currentResourceTypesMap,
       InitialUserActorSetup(),
-      aquarium.userStateStore.insertUserState,
-      None
+      aquarium.userStateStore.insertUserState
     )
 
     // Final touch: Update agreement history in the working user state.
@@ -387,8 +386,7 @@ class UserActor extends ReflectiveRoleableActor {
         this._userStateBootstrap,
         currentResourcesMap,
         chargingReason,
-        stdUserStateStoreFunc,
-        None
+        stdUserStateStoreFunc
       )
 
       updateLatestResourceEventIDFrom(rcEvent)
@@ -401,7 +399,6 @@ class UserActor extends ReflectiveRoleableActor {
         this._workingUserState,
         chargingReason,
         nowBillingMonthInfo,
-        None,
         true
       )
 
index 99e9d39..7e80e69 100644 (file)
 
 package gr.grnet.aquarium.charging
 
-import scala.collection.immutable
+import gr.grnet.aquarium.policy.{ResourceType, EffectivePriceTable, FullPriceTable}
 import scala.collection.mutable
-
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.{Aquarium, AquariumInternalError, AquariumException}
-import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
-import com.ckkloverdos.key.{TypedKey, TypedKeySkeleton}
-import gr.grnet.aquarium.util._
-import gr.grnet.aquarium.util.date.TimeHelpers
-import gr.grnet.aquarium.charging.wallet.WalletEntry
-import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
-import gr.grnet.aquarium.charging.state.AgreementHistory
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.store.PolicyStore
-import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
+import gr.grnet.aquarium.Aquarium
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.charging.wallet.WalletEntry
+import com.ckkloverdos.key.TypedKeySkeleton
 
 /**
  * A charging behavior indicates how charging for a resource will be done
- * wrt the various states a resource can be.
+ * wrt the various states a resource instance can be.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-abstract class ChargingBehavior(
-    final val alias: String,
-    final val inputs: Set[ChargingInput],
-    final val selectorHierarchy: List[List[String]] = Nil) extends Loggable {
-
-  final val inputNames = inputs.map(_.name)
-
-  @inline private[this] 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.discrete ⇒
-       val credits = currentValue * unitPrice
-       val explanation = "Value(%s) * Unit(%s)".format(currentValue, 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)
+trait ChargingBehavior {
+  def alias: String
 
-       (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]
-
-  /**
-   *
-   * @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,
-      billingMonthInfo: BillingMonthInfo,
-      referenceTimeslot: Timeslot,
-      resourceType: ResourceType,
-      agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
-      previousValue: Double,
-      totalCredits: Double,
-      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)
-
-    var _oldTotalCredits = totalCredits
-
-    var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails)
-    setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount)
-
-    val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(
-      referenceTimeslot.from.getTime,
-      referenceTimeslot.to.getTime
-    )
-
-    val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ {
-      this.selectEffectivePriceTable(
-        fullPriceTable,
-        chargingData,
-        currentResourceEvent,
-        referenceTimeslot,
-        previousValue,
-        totalCredits,
-        _oldAccumulatingAmount,
-        _newAccumulatingAmount
-      )
-    }
-
-    val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
-      referenceTimeslot,
-      resourceType,
-      policyByTimeslot,
-      agreementByTimeslot,
-      effectivePriceTableSelector
-    )
-
-    val fullChargeslots = initialChargeslots.map {
-      case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _, _) ⇒
-        val timeDeltaMillis = stopMillis - startMillis
-
-        val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
-          _oldTotalCredits,       // FIXME ??? Should recalculate ???
-          _oldAccumulatingAmount, // FIXME ??? Should recalculate ???
-          _newAccumulatingAmount, // FIXME ??? Should recalculate ???
-          timeDeltaMillis,
-          previousValue,
-          currentValue,
-          unitPrice,
-          currentDetails
-        )
-
-        val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation)
-        newChargeslot
-    }
-
-    if(fullChargeslots.length == 0) {
-      throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
-    }
-
-    val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum
-    val newTotalCredits = _oldTotalCredits - sumOfCreditsToSubtract
-
-    val newWalletEntry = WalletEntry(
-      userID,
-      sumOfCreditsToSubtract,
-      _oldTotalCredits,
-      newTotalCredits,
-      TimeHelpers.nowMillis(),
-      referenceTimeslot,
-      billingMonthInfo.year,
-      billingMonthInfo.month,
-      fullChargeslots,
-      previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)),
-      resourceType,
-      currentResourceEvent.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]]
-  }
-
-  protected def setChargingData[T: Manifest](
-      chargingData: mutable.Map[String, Any],
-      envKey: TypedKey[T],
-      value: T
-  ) = {
-
-    chargingData(envKey.name) = value
-  }
+  def inputs: Set[ChargingInput]
 
   def selectEffectivePriceTable(
       fullPriceTable: FullPriceTable,
@@ -280,33 +66,11 @@ abstract class ChargingBehavior(
       totalCredits: Double,
       oldAccumulatingAmount: Double,
       newAccumulatingAmount: Double
-  ): EffectivePriceTable = {
+  ): EffectivePriceTable
 
-    val selectorPath = computeSelectorPath(
-      chargingData,
-      currentResourceEvent,
-      referenceTimeslot,
-      previousValue,
-      totalCredits,
-      oldAccumulatingAmount,
-      newAccumulatingAmount
-    )
-
-    fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
-  }
 
   /**
    *
-   * @param aquarium
-   * @param currentResourceEvent
-   * @param resourceType
-   * @param billingMonthInfo
-   * @param previousResourceEventOpt
-   * @param userAgreements
-   * @param chargingData
-   * @param totalCredits
-   * @param walletEntryRecorder
-   * @param clogOpt
    * @return The number of wallet entries recorded and the new total credits
    */
   def chargeResourceEvent(
@@ -318,226 +82,14 @@ abstract class ChargingBehavior(
       userAgreements: AgreementHistory,
       chargingData: mutable.Map[String, Any],
       totalCredits: Double,
-      walletEntryRecorder: WalletEntry ⇒ Unit,
-      clogOpt: Option[ContextualLogger] = None
-  ): (Int, Double) = {
-
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "chargeResourceEvent(%s)", currentResourceEvent.id)
-    val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
-
-    val isBillable = this.isBillableEvent(currentResourceEvent)
-    val retval = if(!isBillable) {
-      // The resource event is not billable.
-      clog.debug("Ignoring not billable %s", currentResourceEventDebugInfo)
-      (0, totalCredits)
-    } else {
-      // The resource event is billable.
-      // Find the previous event if needed.
-      // This is (potentially) needed to calculate new credit amount and new resource instance amount
-      if(this.needsPreviousEventForCreditAndAmountCalculation) {
-        if(previousResourceEventOpt.isDefined) {
-          val previousResourceEvent = previousResourceEventOpt.get
-          val previousValue = previousResourceEvent.value
-
-          clog.debug("I have previous event %s", previousResourceEvent.toDebugString)
-
-          computeChargeslots(
-            chargingData,
-            previousResourceEventOpt,
-            currentResourceEvent,
-            billingMonthInfo,
-            Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
-            resourceType,
-            userAgreements.agreementByTimeslot,
-            previousValue,
-            totalCredits,
-            aquarium.policyStore,
-            walletEntryRecorder
-          )
-        } else {
-          // 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.
-          val actualFirstEvent = currentResourceEvent
-
-          // FIXME: Why && ?
-          if(this.isBillableFirstEvent(actualFirstEvent) && this.mustGenerateDummyFirstEvent) {
-            clog.debug("First event of its kind %s", currentResourceEventDebugInfo)
-
-            val dummyFirst = this.constructDummyFirstEventFor(currentResourceEvent, billingMonthInfo.monthStartMillis)
-            clog.debug("Dummy first event %s", dummyFirst.toDebugString)
-
-            val previousResourceEvent = dummyFirst
-            val previousValue = previousResourceEvent.value
-
-            computeChargeslots(
-              chargingData,
-              Some(previousResourceEvent),
-              currentResourceEvent,
-              billingMonthInfo,
-              Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
-              resourceType,
-              userAgreements.agreementByTimeslot,
-              previousValue,
-              totalCredits,
-              aquarium.policyStore,
-              walletEntryRecorder
-            )
-          } else {
-            clog.debug("Ignoring first event of its kind %s", currentResourceEventDebugInfo)
-            // userStateWorker.updateIgnored(currentResourceEvent)
-            (0, totalCredits)
-          }
-        }
-      } else {
-        // No need for previous event. One event does it all.
-        computeChargeslots(
-          chargingData,
-          None,
-          currentResourceEvent,
-          billingMonthInfo,
-          Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
-          resourceType,
-          userAgreements.agreementByTimeslot,
-          this.getResourceInstanceUndefinedAmount,
-          totalCredits,
-          aquarium.policyStore,
-          walletEntryRecorder
-        )
-      }
-    }
-
-    retval
-  }
-
-  /**
-   * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]]
-   * and the value the respective value. This map will be used to do the actual credit charge calculation
-   * by the respective algorithm.
-   *
-   * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context
-   * has been validated before the call to `makeValueMap` is made.
-   *
-   * @param totalCredits   the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]]
-   * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]]
-   * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]]
-   * @param timeDelta      the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]]
-   * @param previousValue  the value for [[gr.grnet.aquarium.charging.PreviousValueInput]]
-   * @param currentValue   the value for [[gr.grnet.aquarium.charging.CurrentValueInput]]
-   * @param unitPrice      the value for [[gr.grnet.aquarium.charging.UnitPriceInput]]
-   *
-   * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values.
-   */
-  def makeValueMap(
-      totalCredits: Double,
-      oldTotalAmount: Double,
-      newTotalAmount: Double,
-      timeDelta: Double,
-      previousValue: Double,
-      currentValue: Double,
-      unitPrice: Double
-  ): Map[ChargingInput, Any] = {
-
-    ChargingBehavior.makeValueMapFor(
-      this,
-      totalCredits,
-      oldTotalAmount,
-      newTotalAmount,
-      timeDelta,
-      previousValue,
-      currentValue,
-      unitPrice)
-  }
-
-  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
-   */
-  def computeNewAccumulatingAmount(
-      oldAccumulatingAmount: Double,
-      newEventValue: Double,
-      newDetails: 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_is_dummy_first -> "true",
-      ResourceEventModel.Names.details_aquarium_reference_event_id -> actualFirst.id,
-      ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty
-    )
-
-    actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, newOccurredMillis)
-  }
+      walletEntryRecorder: WalletEntry ⇒ Unit
+  ): (Int, Double)
 
-  /**
-   * 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
+  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccuredMillis: Long): ResourceEventModel
 }
 
 object ChargingBehavior {
@@ -560,7 +112,7 @@ object ChargingBehavior {
       currentValue: Double,
       unitPrice: Double
   ): Map[ChargingInput, Any] = {
-    
+
     val inputs = chargingBehavior.inputs
     var map = Map[ChargingInput, Any]()
 
index 5d5c451..ac846b0 100644 (file)
@@ -42,7 +42,6 @@ package gr.grnet.aquarium.charging
 
 object ChargingBehaviorAliases {
   final val vmtime     = "vmtime"
-  final val discrete   = "discrete"
   final val continuous = "continuous"
   final val once       = "once"
 }
diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala
new file mode 100644 (file)
index 0000000..993fe62
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * 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
+
+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.wallet.WalletEntry
+import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
+import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.store.PolicyStore
+import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
+
+/**
+ * A charging behavior indicates how charging for a resource will be done
+ * wrt the various states a resource can be.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+abstract class ChargingBehaviorSkeleton(
+    final val alias: String,
+    final val inputs: Set[ChargingInput],
+    final val selectorHierarchy: List[List[String]] = Nil
+) extends ChargingBehavior with Loggable {
+
+  final val inputNames = inputs.map(_.name)
+
+  @inline private[this] 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]
+
+  /**
+   *
+   * @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,
+      billingMonthInfo: BillingMonthInfo,
+      referenceTimeslot: Timeslot,
+      resourceType: ResourceType,
+      agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
+      previousValue: Double,
+      totalCredits: Double,
+      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)
+
+    var _oldTotalCredits = totalCredits
+
+    var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails)
+    setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount)
+
+    val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(
+      referenceTimeslot.from.getTime,
+      referenceTimeslot.to.getTime
+    )
+
+    val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ {
+      this.selectEffectivePriceTable(
+        fullPriceTable,
+        chargingData,
+        currentResourceEvent,
+        referenceTimeslot,
+        previousValue,
+        totalCredits,
+        _oldAccumulatingAmount,
+        _newAccumulatingAmount
+      )
+    }
+
+    val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
+      referenceTimeslot,
+      resourceType,
+      policyByTimeslot,
+      agreementByTimeslot,
+      effectivePriceTableSelector
+    )
+
+    val fullChargeslots = initialChargeslots.map {
+      case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _, _) ⇒
+        val timeDeltaMillis = stopMillis - startMillis
+
+        val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
+          _oldTotalCredits,       // FIXME ??? Should recalculate ???
+          _oldAccumulatingAmount, // FIXME ??? Should recalculate ???
+          _newAccumulatingAmount, // FIXME ??? Should recalculate ???
+          timeDeltaMillis,
+          previousValue,
+          currentValue,
+          unitPrice,
+          currentDetails
+        )
+
+        val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation)
+        newChargeslot
+    }
+
+    if(fullChargeslots.length == 0) {
+      throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
+    }
+
+    val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum
+    val newTotalCredits = _oldTotalCredits - sumOfCreditsToSubtract
+
+    val newWalletEntry = WalletEntry(
+      userID,
+      sumOfCreditsToSubtract,
+      _oldTotalCredits,
+      newTotalCredits,
+      TimeHelpers.nowMillis(),
+      referenceTimeslot,
+      billingMonthInfo.year,
+      billingMonthInfo.month,
+      fullChargeslots,
+      previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)),
+      resourceType,
+      currentResourceEvent.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]]
+  }
+
+  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],
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      previousValue: Double,
+      totalCredits: Double,
+      oldAccumulatingAmount: Double,
+      newAccumulatingAmount: Double
+  ): EffectivePriceTable = {
+
+    val selectorPath = computeSelectorPath(
+      chargingData,
+      currentResourceEvent,
+      referenceTimeslot,
+      previousValue,
+      totalCredits,
+      oldAccumulatingAmount,
+      newAccumulatingAmount
+    )
+
+    fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
+  }
+
+  /**
+   * A generic implementation for charging a resource event.
+   * TODO: Ditch this in favor of completely ahdoc behaviors.
+   *
+   * @param aquarium
+   * @param currentResourceEvent
+   * @param resourceType
+   * @param billingMonthInfo
+   * @param previousResourceEventOpt
+   * @param userAgreements
+   * @param chargingData
+   * @param totalCredits
+   * @param walletEntryRecorder
+   * @return The number of wallet entries recorded and the new total credits
+   */
+  def chargeResourceEvent(
+      aquarium: Aquarium,
+      currentResourceEvent: ResourceEventModel,
+      resourceType: ResourceType,
+      billingMonthInfo: BillingMonthInfo,
+      previousResourceEventOpt: Option[ResourceEventModel],
+      userAgreements: AgreementHistory,
+      chargingData: mutable.Map[String, Any],
+      totalCredits: Double,
+      walletEntryRecorder: WalletEntry ⇒ Unit
+  ): (Int, Double) = {
+
+    val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
+
+    val isBillable = this.isBillableEvent(currentResourceEvent)
+    val retval = if(!isBillable) {
+      // The resource event is not billable.
+      Debug(logger, "Ignoring not billable %s", currentResourceEventDebugInfo)
+      (0, totalCredits)
+    } else {
+      // The resource event is billable.
+      // Find the previous event if needed.
+      // This is (potentially) needed to calculate new credit amount and new resource instance amount
+      if(this.needsPreviousEventForCreditAndAmountCalculation) {
+        if(previousResourceEventOpt.isDefined) {
+          val previousResourceEvent = previousResourceEventOpt.get
+          val previousValue = previousResourceEvent.value
+
+          Debug(logger, "I have previous event %s", previousResourceEvent.toDebugString)
+
+          computeChargeslots(
+            chargingData,
+            previousResourceEventOpt,
+            currentResourceEvent,
+            billingMonthInfo,
+            Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
+            resourceType,
+            userAgreements.agreementByTimeslot,
+            previousValue,
+            totalCredits,
+            aquarium.policyStore,
+            walletEntryRecorder
+          )
+        } else {
+          // 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.
+          val actualFirstEvent = currentResourceEvent
+
+          // FIXME: Why && ?
+          if(this.isBillableFirstEvent(actualFirstEvent) && this.mustGenerateDummyFirstEvent) {
+            Debug(logger, "First event of its kind %s", currentResourceEventDebugInfo)
+
+            val dummyFirst = this.constructDummyFirstEventFor(currentResourceEvent, billingMonthInfo.monthStartMillis)
+            Debug(logger, "Dummy first event %s", dummyFirst.toDebugString)
+
+            val previousResourceEvent = dummyFirst
+            val previousValue = previousResourceEvent.value
+
+            computeChargeslots(
+              chargingData,
+              Some(previousResourceEvent),
+              currentResourceEvent,
+              billingMonthInfo,
+              Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
+              resourceType,
+              userAgreements.agreementByTimeslot,
+              previousValue,
+              totalCredits,
+              aquarium.policyStore,
+              walletEntryRecorder
+            )
+          } else {
+            Debug(logger, "Ignoring first event of its kind %s", currentResourceEventDebugInfo)
+            // userStateWorker.updateIgnored(currentResourceEvent)
+            (0, totalCredits)
+          }
+        }
+      } else {
+        // No need for previous event. One event does it all.
+        computeChargeslots(
+          chargingData,
+          None,
+          currentResourceEvent,
+          billingMonthInfo,
+          Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
+          resourceType,
+          userAgreements.agreementByTimeslot,
+          this.getResourceInstanceUndefinedAmount,
+          totalCredits,
+          aquarium.policyStore,
+          walletEntryRecorder
+        )
+      }
+    }
+
+    retval
+  }
+
+  /**
+   * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]]
+   * and the value the respective value. This map will be used to do the actual credit charge calculation
+   * by the respective algorithm.
+   *
+   * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context
+   * has been validated before the call to `makeValueMap` is made.
+   *
+   * @param totalCredits   the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]]
+   * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]]
+   * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]]
+   * @param timeDelta      the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]]
+   * @param previousValue  the value for [[gr.grnet.aquarium.charging.PreviousValueInput]]
+   * @param currentValue   the value for [[gr.grnet.aquarium.charging.CurrentValueInput]]
+   * @param unitPrice      the value for [[gr.grnet.aquarium.charging.UnitPriceInput]]
+   *
+   * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values.
+   */
+  def makeValueMap(
+      totalCredits: Double,
+      oldTotalAmount: Double,
+      newTotalAmount: Double,
+      timeDelta: Double,
+      previousValue: Double,
+      currentValue: Double,
+      unitPrice: Double
+  ): Map[ChargingInput, Any] = {
+
+    ChargingBehavior.makeValueMapFor(
+      this,
+      totalCredits,
+      oldTotalAmount,
+      newTotalAmount,
+      timeDelta,
+      previousValue,
+      currentValue,
+      unitPrice)
+  }
+
+  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
+   */
+  def computeNewAccumulatingAmount(
+      oldAccumulatingAmount: Double,
+      newEventValue: Double,
+      newDetails: 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_is_dummy_first -> "true",
+      ResourceEventModel.Names.details_aquarium_reference_event_id -> actualFirst.id,
+      ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty
+    )
+
+    actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, 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
+}
index 91042b5..d87035e 100644 (file)
@@ -39,7 +39,10 @@ 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.policy.ResourceType
-import gr.grnet.aquarium.util.{Lifecycle, Loggable, ContextualLogger}
+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}
@@ -73,26 +76,16 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap: UserStateBootstrap,
       defaultResourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
-    val clog = ContextualLogger.fromOther(
-      clogOpt,
-      logger,
-      "findOrCalculateWorkingUserStateAtEndOfBillingMonth(%s)", billingMonthInfo.toShortDebugString)
-    clog.begin()
-
-    lazy val clogSome = Some(clog)
-
     def computeFullMonthBillingAndSaveState(): WorkingUserState = {
       val workingUserState = replayFullMonthBilling(
         userStateBootstrap,
         billingMonthInfo,
         defaultResourceTypesMap,
         chargingReason,
-        userStateRecorder,
-        clogSome
+        userStateRecorder
       )
 
       val newChargingReason = MonthlyBillChargingReason(chargingReason, billingMonthInfo)
@@ -107,7 +100,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       // We always save the state when it is a full month billing
       val monthlyUserState1 = userStateRecorder.apply(monthlyUserState0)
 
-      clog.debug("Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
+      Debug(logger, "Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
 
       workingUserState
     }
@@ -120,7 +113,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
 
     if(billingMonthStopMillis < userCreationMillis) {
       // If the user did not exist for this billing month, piece of cake
-      clog.debug("User did not exist before %s", userCreationDateCalc)
+      Debug(logger, "User did not exist before %s", userCreationDateCalc)
 
       // TODO: The initial user state might have already been created.
       //       First ask if it exists and compute only if not
@@ -130,13 +123,12 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         InitialUserStateSetup(Some(chargingReason)) // we record the originating calculation reason
       )
 
-      logger.debug("Created (from bootstrap) initial user state %s".format(initialUserState0))
+      Debug(logger, "Created (from bootstrap) initial user state %s", initialUserState0)
 
       // We always save the initial state
       val initialUserState1 = userStateRecorder.apply(initialUserState0)
 
-      clog.debug("Stored initial state = %s", initialUserState1.toJsonString)
-      clog.end()
+      Debug(logger, "Stored initial state = %s", initialUserState1.toJsonString)
 
       return initialUserState1.toWorkingUserState(defaultResourceTypesMap)
     }
@@ -149,10 +141,8 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
     latestUserStateOpt match {
       case None ⇒
         // Not found, must compute
-        clog.debug("No user state found from cache, will have to (re)compute")
-        val result = computeFullMonthBillingAndSaveState
-        clog.end()
-        result
+        Debug(logger, "No user state found from cache, will have to (re)compute")
+        computeFullMonthBillingAndSaveState
 
       case Some(latestUserState) ⇒
         // Found a "latest" user state but need to see if it is indeed the true and one latest.
@@ -169,21 +159,18 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
           case 0 ⇒
             // NOTE: Keep the caller's calculation reason
             val userStateModel = latestUserState.newWithChargingReason(chargingReason)
-            clog.end()
             userStateModel.toWorkingUserState(defaultResourceTypesMap)
 
           // We had more, so must recompute
           case n if n > 0 ⇒
-            clog.debug(
+            Debug(logger,
               "Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n)
-            val workingUserState = computeFullMonthBillingAndSaveState
-            clog.end()
-            workingUserState
+            computeFullMonthBillingAndSaveState
 
           // We had less????
           case n if n < 0 ⇒
             val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n)
-            clog.warn(errMsg)
+            Warn(logger, errMsg)
             throw new AquariumInternalError(errMsg)
         }
     }
@@ -195,15 +182,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
    * @param workingUserState
    * @param chargingReason
    * @param billingMonthInfo
-   * @param clogOpt
    */
   def processResourceEvent(
       resourceEvent: ResourceEventModel,
       workingUserState: WorkingUserState,
       chargingReason: ChargingReason,
       billingMonthInfo: BillingMonthInfo,
-      clogOpt: Option[ContextualLogger],
-      updateLatestMiilis: Boolean
+      updateLatestMillis: Boolean
   ): Unit = {
 
     val resourceTypeName = resourceEvent.resource
@@ -225,11 +210,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       workingUserState.workingAgreementHistory.toAgreementHistory,
       workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo),
       workingUserState.totalCredits,
-      workingUserState.walletEntries += _,
-      clogOpt
+      workingUserState.walletEntries += _
     )
 
-    if(updateLatestMiilis) {
+    if(updateLatestMillis) {
       workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
     }
 
@@ -242,8 +226,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       resourceEvents: Traversable[ResourceEventModel],
       workingUserState: WorkingUserState,
       chargingReason: ChargingReason,
-      billingMonthInfo: BillingMonthInfo,
-      clogOpt: Option[ContextualLogger] = None
+      billingMonthInfo: BillingMonthInfo
   ): Unit = {
 
     var _counter = 0
@@ -253,7 +236,6 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         workingUserState,
         chargingReason,
         billingMonthInfo,
-        clogOpt,
         false
       )
 
@@ -270,8 +252,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       billingMonthInfo: BillingMonthInfo,
       defaultResourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
     replayMonthChargingUpTo(
@@ -280,8 +261,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap,
       defaultResourceTypesMap,
       chargingReason,
-      userStateRecorder,
-      clogOpt
+      userStateRecorder
     )
   }
 
@@ -295,7 +275,6 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
    * @param resourceTypesMap
    * @param chargingReason
    * @param userStateRecorder
-   * @param clogOpt
    * @return
    */
   def replayMonthChargingUpTo(
@@ -304,22 +283,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap: UserStateBootstrap,
       resourceTypesMap: Map[String, ResourceType],
       chargingReason: ChargingReason,
-      userStateRecorder: UserStateModel ⇒ UserStateModel,
-      clogOpt: Option[ContextualLogger]
+      userStateRecorder: UserStateModel ⇒ UserStateModel
   ): WorkingUserState = {
 
     val isFullMonthBilling = billingEndTimeMillis == billingMonthInfo.monthStopMillis
     val userID = userStateBootstrap.userID
 
-    val clog = ContextualLogger.fromOther(
-      clogOpt,
-      logger,
-      "replayMonthChargingUpTo(%s)", TimeHelpers.toYYYYMMDDHHMMSSSSS(billingEndTimeMillis))
-    clog.begin()
-
-    clog.debug("%s", chargingReason)
-
-    val clogSome = Some(clog)
+    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
@@ -328,8 +298,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       userStateBootstrap,
       resourceTypesMap,
       chargingReason,
-      userStateRecorder,
-      clogSome
+      userStateRecorder
     )
 
     // FIXME the below comments
@@ -337,10 +306,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
     // specified in the parameters.
     // NOTE: The calculation reason is not the one we get from the previous user state but the one our caller specifies
 
-    clog.debug("workingUserState=%s", workingUserState)
-    clog.debug("previousBillingMonthUserState(%s) = %s".format(
+    Debug(logger, "workingUserState=%s", workingUserState)
+    Debug(logger, "previousBillingMonthUserState(%s) = %s",
       previousBillingMonthInfo.toShortDebugString,
-      workingUserState)
+      workingUserState
     )
 
     var _rcEventsCounter = 0
@@ -350,14 +319,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       billingEndTimeMillis               // to requested time
     ) { currentResourceEvent ⇒
 
-      clog.debug("Processing %s".format(currentResourceEvent))
+      Debug(logger, "Processing %s", currentResourceEvent)
 
       processResourceEvent(
         currentResourceEvent,
         workingUserState,
         chargingReason,
         billingMonthInfo,
-        clogSome,
         false
       )
 
@@ -368,7 +336,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
     }
 
-    clog.debug("Found %s resource events for month %s".format(_rcEventsCounter, billingMonthInfo.toShortDebugString))
+    Debug(logger, "Found %s resource events for month %s",
+      _rcEventsCounter,
+      billingMonthInfo.toShortDebugString
+    )
 
     if(isFullMonthBilling) {
       // For the remaining events which must contribute an implicit OFF, we collect those OFFs
@@ -379,10 +350,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
       )
 
       if(generatorsOfImplicitEnds.lengthCompare(1) >= 0 || theirImplicitEnds.lengthCompare(1) >= 0) {
-        clog.debug("")
-        clog.debug("Process implicitly issued events")
-        clog.debugSeq("generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
-        clog.debugSeq("theirImplicitEnds", theirImplicitEnds, 0)
+        Debug(logger, "")
+        Debug(logger, "Process implicitly issued events")
+        DebugSeq(logger, "generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
+        DebugSeq(logger, "theirImplicitEnds", theirImplicitEnds, 0)
       }
 
       // Now, the previous and implicitly started must be our base for the following computation, so we create an
@@ -395,15 +366,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo
         theirImplicitEnds,
         specialWorkingUserState,
         chargingReason,
-        billingMonthInfo,
-        clogSome
+        billingMonthInfo
       )
 
       workingUserState.walletEntries ++= specialWorkingUserState.walletEntries
       workingUserState.totalCredits    = specialWorkingUserState.totalCredits
     }
 
-    clog.end()
     workingUserState
   }
 }
index 7241a77..0dcfeba 100644 (file)
@@ -48,7 +48,7 @@ import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 final class ContinuousChargingBehavior
-    extends ChargingBehavior(
+    extends ChargingBehaviorSkeleton(
       ChargingBehaviorAliases.continuous,
       Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
 
diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala b/src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala
deleted file mode 100644 (file)
index f3573ca..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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
-
-import gr.grnet.aquarium.AquariumException
-
-/**
- * Encapsulates the possible states that a resource with an
- * [[gr.grnet.aquarium.charging.VMChargingBehavior]]
- * can be.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-sealed abstract class OnOffPolicyResourceState(val state: String) {
-  def isOn: Boolean = !isOff
-  def isOff: Boolean = !isOn
-}
-
-object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.on) {
-  override def isOn = true
-}
-
-object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.off) {
-  override def isOff = true
-}
-
-object OnOffPolicyResourceState {
-  object Names {
-    final val on  = "on"
-    final val off = "off"
-  }
-}
-
index bc8327c..cbb66bc 100644 (file)
@@ -49,7 +49,7 @@ import gr.grnet.aquarium.policy.FullPriceTable
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 final class OnceChargingBehavior
-    extends ChargingBehavior(
+    extends ChargingBehaviorSkeleton(
       ChargingBehaviorAliases.once,
       Set(ChargingBehaviorNameInput, CurrentValueInput)) {
 
index 1fa1237..0f43afb 100644 (file)
 
 package gr.grnet.aquarium.charging
 
-import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
+import gr.grnet.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 gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable}
 
 /**
  * The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage.
@@ -48,7 +47,7 @@ import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable}
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 final class VMChargingBehavior
-    extends ChargingBehavior(
+    extends ChargingBehaviorSkeleton(
       ChargingBehaviorAliases.vmtime,
       Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput),
       List(List(Power.powerOn, Power.powerOff))) {
index 50c8f4c..b7e8388 100644 (file)
@@ -41,7 +41,7 @@ import gr.grnet.aquarium.util.shortClassNameOf
 
 /**
  * Provides information explaining the reason Aquarium calculated a new
- * [[gr.grnet.aquarium.computation.state.UserState]].
+ * [[gr.grnet.aquarium.charging.state.UserStateModel]].
  */
 case class ChargingReason(
     details: Map[String, Any],
index c0da20d..e9e932f 100644 (file)
 package gr.grnet.aquarium.computation
 
 import collection.immutable.SortedMap
-import com.ckkloverdos.maybe.{NoVal, Maybe}
-import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
-import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.util.Loggable
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import gr.grnet.aquarium.policy._
 import collection.immutable
-import com.ckkloverdos.maybe.Just
 import gr.grnet.aquarium.policy.ResourceType
 import gr.grnet.aquarium.policy.EffectiveUnitPrice
-import gr.grnet.aquarium.charging.{ChargingBehavior, Chargeslot}
+import gr.grnet.aquarium.charging.Chargeslot
 
 /**
  * Methods for converting accounting events to wallet entries.
@@ -67,8 +64,7 @@ object TimeslotComputations extends Loggable {
   def splitTimeslotByPoliciesAndAgreements(
       referenceTimeslot: Timeslot,
       policyTimeslots: List[Timeslot],
-      agreementTimeslots: List[Timeslot],
-      clogM: Maybe[ContextualLogger] = NoVal
+      agreementTimeslots: List[Timeslot]
   ): List[Timeslot] = {
 
     // Align policy and agreement validity timeslots to the referenceTimeslot
@@ -91,12 +87,9 @@ object TimeslotComputations extends Loggable {
       policy: PolicyModel,
       agreement: UserAgreementModel,
       resourceType: ResourceType,
-      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
-      clogOpt: Option[ContextualLogger] = None
+      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
   ): SortedMap[Timeslot, Double] = {
 
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
-
     // Note that most of the code is taken from calcChangeChunks()
     val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType, effectivePriceTableSelector)
     ret map {case (t,p) => (t,p.unitPrice)}
@@ -107,12 +100,9 @@ object TimeslotComputations extends Loggable {
       resourceType: ResourceType,
       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
-      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
-      clogOpt: Option[ContextualLogger] = None
+      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
   ): List[Chargeslot] = {
 
-    val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
-
     val policyTimeslots = policyByTimeslot.keySet
     val agreementTimeslots = agreementByTimeslot.keySet
 
@@ -125,7 +115,7 @@ object TimeslotComputations extends Loggable {
 
     // 1. Round ONE: split time according to overlapping policies and agreements.
     //val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
-    val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
+    val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList)
 
     // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
     //    fine-grained timeslots according to applicable algorithms.
@@ -145,8 +135,7 @@ object TimeslotComputations extends Loggable {
         policy,
         userAgreement,
         resourceType,
-        effectivePriceTableSelector,
-        Some(clog)
+        effectivePriceTableSelector
       )
 
       // Now, the timeslots must be the same
index 2246a63..50000cb 100644 (file)
@@ -111,12 +111,10 @@ trait PolicyModel extends Ordered[PolicyModel] with JsonSupport {
 
 object PolicyModel {
   trait NamesT {
-    final val a = 1
+    final val id = "id"
+    final val parentID = "parentID"
+    final val idInStore = "idInStore"
   }
 
   final object Names extends NamesT
-
-  def fromJsonString(json: String): PolicyModel = {
-    StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json))
-  }
 }
index 0b0667b..d18faf6 100644 (file)
@@ -36,6 +36,7 @@
 package gr.grnet.aquarium.policy
 
 import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
 
 /**
  * Standard implementation of Aquarium policy model.
@@ -54,3 +55,9 @@ case class StdPolicy(
 
   def idInStore = Some(id)
 }
+
+object StdPolicy {
+  def fromJsonString(json: String): StdPolicy = {
+    StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json))
+  }
+}
diff --git a/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala b/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala
deleted file mode 100644 (file)
index c1f9a8b..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * 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.util
-
-import org.slf4j.Logger
-import com.ckkloverdos.maybe.Failed
-
-/**
- * A logger that keeps track of working context and indentation level.
- *
- * This is mostly useful in single-threaded debugging sessions.
-  *
-  * A sample output follows:
-  *
-  * {{{
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF() BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   +++ [Events by OccurredMillis] +++
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()       EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   --- [Events by OccurredMillis] ---
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2012-01)   BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)     BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)       No user state found from cache, will have to (re)compute
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-12)       BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)         BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)           No user state found from cache, will have to (re)compute
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-11)           BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)             BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)               User did not exist before 2011-11-01 00:00:00.000
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)               Returning INITIAL state [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,InitialUserStateSetup,0,None,4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10)             END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-11)             calculationReason = MonthlyBillChargingReason$(2011-11)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-11)             Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-11)             RETURN UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-11)           END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11)         END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-12)         calculationReason = MonthlyBillChargingReason$(2011-12)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-12)         Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-12)         RETURN UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2011-12)       END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12)     END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)     +++ [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] +++
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       Cost policy OnOffCostPolicy for DSLResource(vmtime,Hr,OnOffCostPolicy,true,instanceID)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       PreviousM None
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)       Ignoring first event of its kind EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -         walletEntriesForResourceEvent(0)     --- [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] ---
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2012-01)     calculationReason = MonthlyBillChargingReason$(2012-01)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2012-01)     Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2012-01)     RETURN UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -            replayFullMonthBilling(2012-01)   END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   _id = 4fa7e12ba0eee3db73fbe8d0
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   parentId = Some(4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   credits = 0.0
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   changeReason = MonthlyBillChargingReason$(2012-01)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   implicitlyIssued [#=0] = List()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   latestResourceEvents [#=1]:
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()     EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:20 g.g.a.user.UserStateComputationsTest -                          testOrphanOFF()   newWalletEntries [#=0] = List()
- * }}}
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-final class ContextualLogger(val logger: Logger, fmt: String, args: Any*) {
-  val ctx = ContextualLogger.fixCtx(fmt.format(args:_*))
-
-  private[this] var _nesting = 0
-
-  private def nestMsg(fmt: String, args: Any*) = {
-    val msg = fmt.format(args: _*)
-    _nesting match {
-      case 0 ⇒
-        msg
-      case n ⇒
-        val buffer = new java.lang.StringBuilder(n + msg.size)
-        var _i = 0
-        while(_i < (n * 2)) {
-          buffer.append(' ')
-          _i = _i + 1
-        }
-        buffer.append(msg)
-        buffer.toString
-    }
-  }
-
-  def isDebugEnabled = logger.isDebugEnabled
-
-  def nesting = _nesting
-
-  def indentAs(other: ContextualLogger): this.type = {
-    _nesting = other.nesting
-    this
-  }
-
-  def indent(): this.type   = {
-    _nesting = _nesting + 1
-    this
-  }
-
-  def unindent(): this.type = {
-    _nesting = _nesting - 1
-    this
-  }
-  
-  def withIndent[A](f: => A): Unit = {
-    import com.ckkloverdos.maybe.effect
-    this.indent()
-    effect(f){}{unindent()}
-  }
-
-  def debug(fmt: String, args: Any*): Unit = {
-    if(logger.isDebugEnabled) {
-      val msg = ctx + " " + nestMsg(fmt, args:_*)
-      logger.debug(msg)
-    }
-  }
-
-  def info(fmt: String, args: Any*): Unit = {
-    if(logger.isInfoEnabled) {
-      val msg = ctx + " " + nestMsg(fmt, args:_*)
-      logger.info(msg)
-    }
-  }
-
-  def warn(fmt: String, args: Any*): Unit = {
-    if(logger.isWarnEnabled) {
-      val msg = ctx + " " + nestMsg(fmt, args:_*)
-      logger.warn(msg)
-    }
-  }
-
-  def error(fmt: String, args: Any*): Unit = {
-    if(logger.isErrorEnabled) {
-      val msg = ctx + " " + nestMsg(fmt, args:_*)
-      logger.error(msg)
-    }
-  }
-
-  def error(t: Throwable, fmt: String, args: Any*): Unit = {
-    if(logger.isErrorEnabled) {
-      val msg = ctx + " " + nestMsg(fmt, args:_*)
-      logger.error(msg, t)
-    }
-  }
-  
-  def error(failed: Failed): Unit = {
-    this.error(failed.exception, "")
-  }
-
-  def begin(message: String = ""): Unit = {
-    if(message == "") debug("BEGIN") else debug("+++ [%s] +++", message)
-    indent()
-  }
-
-  def end(message: String = ""): Unit = {
-    unindent()
-    if(message == "") debug("END") else debug("--- [%s] ---", message)
-  }
-
-  def debugMap[K, V](name: String, map: scala.collection.Map[K, V], oneLineLimit: Int = 3): Unit = {
-    if(this.isDebugEnabled) {
-      val mapSize = map.size
-      if(mapSize <= oneLineLimit) {
-        this.debug("%s [#=%s] = %s", name, mapSize, map)
-      } else {
-        this.debug("%s [#=%s]:", name, mapSize)
-        val maxKeySize = maxStringSize(map.keySet)
-        this.withIndent {
-          for((k, v) <- map) {
-            this.debug("%s -> %s", rpad(k.toString, maxKeySize), v)
-          }
-        }
-      }
-    }
-  }
-
-  def debugSeq[T](name: String, seq: scala.collection.Seq[T], oneLineLimit: Int = 3): Unit = {
-    if(this.isDebugEnabled) {
-      val seqSize = seq.size
-      if(seqSize <= oneLineLimit) {
-        this.debug("%s [#=%s] = %s", name, seqSize, seq)
-      } else {
-        this.debug("%s [#=%s]: ", name, seqSize)
-        this.withIndent(seq.foreach(this.debug("%s", _)))
-      }
-    }
-  }
-
-  def debugSet[T](name: String, set: scala.collection.Set[T], oneLineLimit: Int = 3): Unit = {
-    if(this.isDebugEnabled) {
-      val setSize = set.size
-      if(setSize <= oneLineLimit) {
-        this.debug("%s [#=%s] = %s", name, setSize, set)
-      } else {
-        this.debug("%s [#=%s]: ", name, setSize)
-        this.withIndent(set.foreach(this.debug("%s", _)))
-      }
-    }
-  }
-}
-
-/**
- * Companion object of [[gr.grnet.aquarium.util.ContextualLogger]].
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-object ContextualLogger {
-  final val MaxCtxLength = 40
-  final val ELLIPSIS = "…" // U+2026
-  
-  def fixCtx(ctx: String): String = {
-    val ctxLen = ctx.length()
-    if(ctxLen == MaxCtxLength) {
-      ctx
-    } else if(ctxLen > MaxCtxLength) {
-      ELLIPSIS + ctx.substring(ctxLen - MaxCtxLength + 1, ctxLen)
-    } else {
-      val buffer = new java.lang.StringBuilder(MaxCtxLength)
-      val prefixLen = MaxCtxLength - ctxLen
-      var _i = 0
-      while(_i < prefixLen) {
-        buffer.append(' ')
-        _i = _i + 1
-      }
-      buffer.append(ctx)
-      buffer.toString
-    }
-  }
-  
-  def fromOther(clogOpt: Option[ContextualLogger], logger: Logger,  fmt: String, args: Any*): ContextualLogger = {
-    clogOpt match {
-      case Some(clog) ⇒
-        new ContextualLogger(clog.logger, fmt, args:_*).indentAs(clog)
-
-      case None ⇒
-        new ContextualLogger(logger, fmt, args:_*)
-    }
-  }
-}
index d05ada7..2065d5f 100644 (file)
@@ -84,4 +84,84 @@ object LogHelpers {
     logger.error(message + "\n{}", chainOfCausesForLogging(t))
     logger.error(message, t)
   }
+
+  @inline
+  final def Debug(logger: Logger, fmt: String, args: Any*): Unit = {
+    if(logger.isDebugEnabled) {
+      logger.debug(fmt.format(args:_*))
+    }
+  }
+
+  @inline
+  final def Info(logger: Logger, fmt: String, args: Any*): Unit = {
+    if(logger.isInfoEnabled) {
+      logger.info(fmt.format(args:_*))
+    }
+  }
+
+  @inline
+  final def Warn(logger: Logger, fmt: String, args: Any*): Unit = {
+    if(logger.isWarnEnabled) {
+      logger.warn(fmt.format(args:_*))
+    }
+  }
+
+  @inline
+  final def Error(logger: Logger, fmt: String, args: Any*): Unit = {
+    if(logger.isErrorEnabled) {
+      logger.error(fmt.format(args:_*))
+    }
+  }
+
+  @inline
+  final def Error(logger: Logger, t: Throwable, fmt: String, args: Any*): Unit = {
+    if(logger.isErrorEnabled) {
+      logger.error(fmt.format(args:_*), t)
+    }
+  }
+
+  final def DebugMap[K, V](
+      logger: Logger,
+      name: String,
+      map: scala.collection.Map[K, V],
+      oneLineLimit: Int = 3
+  ): Unit = {
+
+    if(logger.isDebugEnabled) {
+      val mapSize = map.size
+      if(mapSize <= oneLineLimit) {
+        Debug(logger, "%s [#=%s] = %s", name, mapSize, map)
+      } else {
+        logger.debug("%s [#=%s]:", name, mapSize)
+        val maxKeySize = maxStringSize(map.keySet)
+        for((k, v) <- map) {
+          this.Debug(logger, "%s -> %s", rpad(k.toString, maxKeySize), v)
+        }
+      }
+    }
+  }
+
+  final def DebugSeq[T](logger: Logger, name: String, seq: scala.collection.Seq[T], oneLineLimit: Int = 3): Unit = {
+    if(logger.isDebugEnabled) {
+      val seqSize = seq.size
+      if(seqSize <= oneLineLimit) {
+        Debug(logger, "%s [#=%s] = %s", name, seqSize, seq)
+      } else {
+        Debug(logger, "%s [#=%s]: ", name, seqSize)
+        seq.foreach(Debug(logger, "%s", _))
+      }
+    }
+  }
+
+  final def DebugSet[T](logger: Logger, name: String, set: scala.collection.Set[T], oneLineLimit: Int = 3): Unit = {
+    if(logger.isDebugEnabled) {
+      val setSize = set.size
+      if(setSize <= oneLineLimit) {
+        Debug(logger, "%s [#=%s] = %s", name, setSize, set)
+      } else {
+        Debug(logger, "%s [#=%s]: ", name, setSize)
+        set.foreach(Debug(logger, "%s", _))
+      }
+    }
+  }
 }
index 3dc1160..4f228b1 100644 (file)
@@ -81,9 +81,7 @@ class StdPolicyTest {
   @Test
   def testJson(): Unit = {
     val json = policy.toJsonString
-    val obj = PolicyModel.fromJsonString(json)
-
-    println(json)
+    val obj = StdPolicy.fromJsonString(json)
 
     assert(policy == obj)
   }
diff --git a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala
deleted file mode 100644 (file)
index da4c635..0000000
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * 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.user
-
-import gr.grnet.aquarium.store.memory.MemStoreProvider
-import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
-import gr.grnet.aquarium.simulation._
-import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
-import org.junit.{Assert, Ignore, Test}
-import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableChargingBehaviorAlgorithm, CostPolicyAlgorithmCompiler}
-import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging._
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, FullPriceTable, PolicyModel}
-import gr.grnet.aquarium.charging.reason.{NoSpecificChargingReason, MonthlyBillChargingReason}
-import gr.grnet.aquarium.charging.state.WorkingUserState
-import gr.grnet.aquarium.policy.FullPriceTable._
-import gr.grnet.aquarium.Timespan
-import gr.grnet.aquarium.simulation.AquariumSim
-import scala.Some
-import gr.grnet.aquarium.policy.EffectiveUnitPrice
-import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.policy.StdUserAgreement
-import gr.grnet.aquarium.charging.state.UserStateBootstrap
-import gr.grnet.aquarium.policy.EffectivePriceTable
-import gr.grnet.aquarium.policy.StdPolicy
-import gr.grnet.aquarium.simulation.ClientSim
-
-
-/**
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-class UserStateComputationsTest extends Loggable {
-  final val DoubleDelta = 0.001
-
-  final val BandwidthUnitPrice = 3.3 //
-  final val VMTimeUnitPrice    = 1.5 //
-  final val DiskspaceUnitPrice = 2.7 //
-
-  final val OnOffUnitPrice = VMTimeUnitPrice
-  final val ContinuousUnitPrice = DiskspaceUnitPrice
-  final val DiscreteUnitPrice = BandwidthUnitPrice
-
-  final val DefaultPolicy: PolicyModel = StdPolicy(
-    id = "policy-1",
-    parentID = None,
-    validityTimespan = Timespan(0),
-    resourceTypes = Set(
-      ResourceType("vmtime", "Hr", classOf[VMChargingBehavior].getName),
-      ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
-    ),
-    chargingBehaviors = Set(
-      classOf[VMChargingBehavior].getName,
-      classOf[ContinuousChargingBehavior].getName,
-      classOf[OnceChargingBehavior].getName
-    ),
-    roleMapping = Map(
-      "default" -> FullPriceTable(Map(
-        "diskspace" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)),
-        "vmtime"    -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil))
-      ))
-    )
-  )
-
-  val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel).
-    update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
-    build()
-
-  val ResourceEventStore = aquarium.resourceEventStore
-
-  val ChargingService = aquarium.chargingService
-
-  val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
-    def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
-      hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
-
-    final val creditsForDiskspace = creditsForContinuous(_, _)
-    
-    def creditsForDiscrete(currentValue: Double) =
-      currentValue * DiscreteUnitPrice
-
-    final val creditsForBandwidth = creditsForDiscrete(_)
-
-    def creditsForOnOff(timeDelta: Double) =
-      hrs(timeDelta) * OnOffUnitPrice
-
-    final val creditsForVMTime = creditsForOnOff(_)
-
-    @inline private[this]
-    def hrs(millis: Double) = millis / 1000 / 60 / 60
-
-    def apply(vars: Map[ChargingInput, Any]): Double = {
-      vars.apply(ChargingBehaviorNameInput) match {
-        case ChargingBehaviorAliases.continuous ⇒
-          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
-          val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
-          val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
-
-          Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
-
-          creditsForContinuous(timeDelta, oldTotalAmount)
-
-        case ChargingBehaviorAliases.discrete ⇒
-          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
-          val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
-
-          Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
-
-          creditsForDiscrete(currentValue)
-
-        case ChargingBehaviorAliases.vmtime ⇒
-          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
-          val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
-
-          Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
-
-          creditsForOnOff(timeDelta)
-
-        case ChargingBehaviorAliases.once ⇒
-          val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
-          currentValue
-
-        case name ⇒
-          throw new AquariumException("Unknown cost policy %s".format(name))
-      }
-    }
-
-    override def toString = "DefaultAlgorithm(%s)".format(
-      Map(
-        ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
-        ChargingBehaviorAliases.discrete   -> "currentValue * %s".format(DiscreteUnitPrice),
-        ChargingBehaviorAliases.vmtime      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
-        ChargingBehaviorAliases.once       -> "currentValue"))
-  }
-
-  val DefaultCompiler  = new CostPolicyAlgorithmCompiler {
-    def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
-      DefaultAlgorithm
-    }
-  }
-  //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
-
-  val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
-
-  // For this to work, the definitions must match those in the YAML above.
-  // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
-  val VMTimeResourceSim    = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
-  val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
-
-  // There are two client services, synnefo and pithos.
-  val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
-  val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
-  val Pithos  = ClientSim("pithos" )(TheUIDGenerator)
-
-  val StartOfBillingYearDateCalc = new MutableDateCalc(2012,  1, 1)
-  val UserCreationDate           = new MutableDateCalc(2011, 11, 1).toDate
-
-  val BillingMonthInfoJan = {
-    val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
-    BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
-  }
-  val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012,  2, 1))
-  val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012,  3, 1))
-
-  // Store the default policy
-  val policyDateCalc        = StartOfBillingYearDateCalc.copy
-  val policyOccurredMillis  = policyDateCalc.toMillis
-  val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
-  val policyValidToMillis   = policyDateCalc.copy.goNextYear.toMillis
-
-  aquarium.policyStore.insertPolicy(DefaultPolicy)
-
-  val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim), aquarium.resourceEventStore)
-  val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
-
-  val UserCKKL  = AquariumSim_.newUser("CKKL", UserCreationDate)
-
-//  val InitialUserState = UserState.createInitialUserState(
-//    userID = UserCKKL.userID,
-//    userCreationMillis = UserCreationDate.getTime,
-//    totalCredits = 0.0,
-//    initialRole = "default",
-//    initialAgreement = DSLAgreement.DefaultAgreementName
-//  )
-
-  val UserStateBootstrapper = UserStateBootstrap(
-    userID = UserCKKL.userID,
-    userCreationMillis = UserCreationDate.getTime(),
-    initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef),
-    initialCredits = 0.0
-  )
-
-  // By convention
-  // - synnefo is for VMTime and Bandwidth
-  // - pithos is for Diskspace
-  val VMTimeInstanceSim    = VMTimeResourceSim.newInstance   ("VM.1",   UserCKKL, Synnefo)
-  val DiskInstanceSim      = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
-
-  private[this]
-  def showUserState(clog: ContextualLogger, workingUserState: WorkingUserState) {
-//    val id = workingUserState.id
-//    val parentId = workingUserState.parentUserStateIDInStore
-//    val credits = workingUserState.totalCredits
-//    val newWalletEntries = workingUserState.newWalletEntries.map(_.toDebugString)
-//    val changeReason = workingUserState.lastChangeReason
-//    val implicitlyIssued = workingUserState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
-//    val latestResourceEvents = workingUserState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
-//
-//    clog.debug("_id = %s", id)
-//    clog.debug("parentId = %s", parentId)
-//    clog.debug("credits = %s", credits)
-//    clog.debug("changeReason = %s", changeReason)
-//    clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
-//    clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
-//    clog.debugSeq("newWalletEntries", newWalletEntries, 0)
-  }
-
-  private[this]
-  def showResourceEvents(clog: ContextualLogger): Unit = {
-    clog.debug("")
-    clog.begin("Events by OccurredMillis")
-    clog.withIndent {
-      for(event <- UserCKKL.myResourceEventsByOccurredDate) {
-        clog.debug(event.toDebugString)
-      }
-    }
-    clog.end("Events by OccurredMillis")
-    clog.debug("")
-  }
-
-  private[this]
-  def doFullMonthlyBilling(
-      clog: ContextualLogger,
-      billingMonthInfo: BillingMonthInfo,
-      billingTimeMillis: Long) = {
-
-    ChargingService.replayMonthChargingUpTo(
-      billingMonthInfo,
-      billingTimeMillis,
-      UserStateBootstrapper,
-      DefaultResourcesMap,
-      MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo),
-      aquarium.userStateStore.insertUserState,
-      Some(clog)
-    )
-  }
-
-  private[this]
-  def expectCredits(
-      clog: ContextualLogger,
-      creditsConsumed: Double,
-      workingUserState: WorkingUserState,
-      accuracy: Double = 0.001
-  ): Unit = {
-
-    val computed = workingUserState.totalCredits
-    Assert.assertEquals(-creditsConsumed, computed, accuracy)
-
-    clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
-  }
-
-  private[this]
-  def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
-
-  private[this]
-  def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
-
-  /**
-   * Test a sequence of ON, OFF vmtime events.
-   */
-  @Ignore
-  @Test
-  def testFullOnOff: Unit = {
-    val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
-    clog.begin()
-
-    ResourceEventStore.clearResourceEvents()
-    val OnOffDurationHrs = 10
-    val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
-
-    VMTimeInstanceSim.newONOFF(
-      new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
-      OnOffDurationHrs
-    )
-
-    val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
-
-    showResourceEvents(clog)
-
-    val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
-    showUserState(clog, workingUserState)
-
-    expectCredits(clog, credits, workingUserState)
-
-    clog.end()
-  }
-
-  @Ignore
-  @Test
-  def testLonelyON: Unit = {
-    val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
-    clog.begin()
-
-    ResourceEventStore.clearResourceEvents()
-    
-    val JanStart = new MutableDateCalc(2012, 01, 01)
-    val JanEnd = JanStart.copy.goEndOfThisMonth
-    val JanStartDate = JanStart.toDate
-    val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
-    val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
-
-    VMTimeInstanceSim.newON(JanStartDate)
-
-    val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
-
-    showResourceEvents(clog)
-
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
-    showUserState(clog, userState)
-
-    expectCredits(clog, credits, userState)
-
-    clog.end()
-  }
-
-//  @Ignore
-  @Test
-  def testOrphanOFF: Unit = {
-    val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
-    clog.begin()
-
-    ResourceEventStore.clearResourceEvents()
-
-    val JanStart = new MutableDateCalc(2012, 01, 01)
-    val JanEnd = JanStart.copy.goEndOfThisMonth
-    val JanStartDate = JanStart.toDate
-    val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
-    val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
-
-    VMTimeInstanceSim.newOFF(JanStartDate)
-
-    // This is an orphan event, so no credits will be charged
-    val credits = 0
-
-    showResourceEvents(clog)
-
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
-    showUserState(clog, userState)
-
-    expectCredits(clog, credits, userState)
-
-    clog.end()
-  }
-
-  @Ignore
-  @Test
-  def testOne: Unit = {
-    val clog = ContextualLogger.fromOther(None, logger, "testOne()")
-    clog.begin()
-
-    // Let's create our dates of interest
-    val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
-    val VMStartDate = VMStartDateCalc.toDate
-
-    // Within January, create one VM ON-OFF ...
-    VMTimeInstanceSim.newONOFF(VMStartDate, 9)
-
-    val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
-    val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
-    val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
-    val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
-
-    // ... and two diskspace changes
-    DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
-    DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
-
-    // ... and one "future" event
-    DiskInstanceSim.consumeMB(
-      StartOfBillingYearDateCalc.copy.
-        goNextMonth.goPlusDays(6).
-        goPlusHours(7).
-        goPlusMinutes(7).
-        goPlusSeconds(7).
-        goPlusMillis(7).toDate,
-      777)
-
-    showResourceEvents(clog)
-
-    // Policy: from 2012-01-01 to Infinity
-
-    clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1)
-
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
-    showUserState(clog, userState)
-
-    clog.end()
-  }
-}
\ No newline at end of file