Test case for new policy configuration
authorChristos KK Loverdos <loverdos@gmail.com>
Wed, 11 Jul 2012 11:46:00 +0000 (14:46 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Wed, 11 Jul 2012 11:46:00 +0000 (14:46 +0300)
31 files changed:
src/main/resources/aquarium.properties
src/main/resources/policy.scala
src/main/scala/gr/grnet/aquarium/Aquarium.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala [moved from src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorNames.scala with 98% similarity]
src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehavior.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehaviorValues.scala [moved from src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceStateNames.scala with 90% similarity]
src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala
src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala [new file with mode: 0644]
src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala
src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala
src/main/scala/gr/grnet/aquarium/computation/state/UserStateWorker.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/SimpleExecutableChargingBehaviorAlgorithm.scala [moved from src/main/scala/gr/grnet/aquarium/logic/accounting/algorithm/SimpleExecutableChargingBehaviorAlgorithm$.scala with 82% similarity]
src/main/scala/gr/grnet/aquarium/policy/EffectiveUnitPrice.scala
src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala
src/main/scala/gr/grnet/aquarium/policy/ResourceType.scala
src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala
src/main/scala/gr/grnet/aquarium/service/AkkaService.scala
src/main/scala/gr/grnet/aquarium/simulation/ResourceSim.scala
src/main/scala/gr/grnet/aquarium/simulation/StdBandwidthResourceSim.scala
src/main/scala/gr/grnet/aquarium/simulation/StdDiskspaceResourceSim.scala
src/main/scala/gr/grnet/aquarium/simulation/StdVMTimeResourceSim.scala
src/main/scala/gr/grnet/aquarium/store/memory/MemStoreProvider.scala
src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBPolicy.scala
src/test/resources/aquarium.properties
src/test/scala/gr/grnet/aquarium/logic/test/DSLTimeFrameTest.scala
src/test/scala/gr/grnet/aquarium/logic/test/TimeslotTest.scala
src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala [new file with mode: 0644]
src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala

index dfaadec..d0d2c1d 100644 (file)
@@ -89,5 +89,8 @@ store.provider.class=gr.grnet.aquarium.store.mongodb.MongoDBStoreProvider
 # Override the user event store (if present, it will not be given by the store provider above)
 #policy.store.class=
 
+# A time period in milliseconds for which we can tolerate stale parts regarding user state.
+user.state.timestamp.threshold=1
+
 # Administrative REST API authorization cookie
 admin.cookie=1
\ No newline at end of file
index 68176fc..1f834a9 100644 (file)
@@ -6,29 +6,29 @@ import gr.grnet.aquarium.Timespan
 // This will be dynamically interpreted during Aquarium startup
 
 StdPolicy(
-    id = "policy-1",
-    parentID = None,
+  id = "policy-1",
+  parentID = None,
 
-    validityTimespan = Timespan(0),
+  validityTimespan = Timespan(0),
 
-    resourceTypes = Set(
-      ResourceType("bandwidth", "MB/Hr", DiscreteChargingBehavior),
-      ResourceType("vmtime",    "Hr",    OnOffChargingBehavior),
-      ResourceType("diskspace", "MB/Hr", ContinuousChargingBehavior)
-    ),
+  resourceTypes = Set(
+    ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+    ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+    ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
+  ),
 
-    chargingBehaviorClasses = Set(
-      DiscreteChargingBehavior.getClass.getName,
-      OnOffChargingBehavior.getClass.getName,
-      ContinuousChargingBehavior.getClass.getName,
-      OnceChargingBehavior.getClass.getName
-    ),
+  chargingBehaviors = Set(
+    classOf[DiscreteChargingBehavior].getName,
+    classOf[OnOffChargingBehavior].getName,
+    classOf[ContinuousChargingBehavior].getName,
+    classOf[OnceChargingBehavior].getName
+  ),
 
-    roleMapping = Map(
-      "default" -> FullPriceTable(Map(
-        "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
-        "vmtime"    -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
-        "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
-      ))
-    )
-  )
\ No newline at end of file
+  roleMapping = Map(
+    "default" -> FullPriceTable(Map(
+      "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+      "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+      "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
+    ))
+  )
+)
\ No newline at end of file
index a31281e..7fac204 100644 (file)
@@ -51,6 +51,7 @@ import gr.grnet.aquarium.ResourceLocator._
 import com.ckkloverdos.sys.SysProp
 import gr.grnet.aquarium.service.event.AquariumCreatedEvent
 import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, UserAgreementModel, ResourceType}
+import gr.grnet.aquarium.charging.ChargingBehavior
 
 /**
  *
@@ -60,6 +61,8 @@ import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreemen
 final class Aquarium(env: Env) extends Lifecycle with Loggable {
   import Aquarium.EnvKeys
 
+  @volatile private[this] var _chargingBehaviorMap = Map[String, ChargingBehavior]()
+
   private[this] val _isStopping = new AtomicBoolean(false)
 
   override def toString = "%s/v%s".format(getClass.getName, version)
@@ -179,7 +182,14 @@ final class Aquarium(env: Env) extends Lifecycle with Loggable {
   /**
    * Reflectively provide a new instance of a class and configure it appropriately.
    */
-  def newInstance[C <: AnyRef](_class: Class[C], className: String): C = {
+  def newInstance[C <: AnyRef](_class: Class[C]): C = {
+    newInstance(_class.getName)
+  }
+
+  /**
+   * Reflectively provide a new instance of a class and configure it appropriately.
+   */
+  def newInstance[C <: AnyRef](className: String): C = {
     val originalProps = apply(EnvKeys.originalProps)
 
     val instanceM = MaybeEither(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
@@ -248,6 +258,24 @@ final class Aquarium(env: Env) extends Lifecycle with Loggable {
     "default"
   }
 
+  def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = {
+    val className = resourceType.chargingBehavior
+    _chargingBehaviorMap.get(className) match {
+      case Some(chargingBehavior) ⇒
+        chargingBehavior
+
+      case _ ⇒
+        // It does not matter if this is entered by multiple threads and more than one instance of the same class
+        // is created. The returned instance is not meant to be cached.
+        val chargingBehavior = newInstance[ChargingBehavior](className)
+        _chargingBehaviorMap synchronized {
+          _chargingBehaviorMap = _chargingBehaviorMap.updated(className, chargingBehavior)
+        }
+
+        chargingBehavior
+    }
+  }
+
   def defaultClassLoader = apply(EnvKeys.defaultClassLoader)
 
   def resourceEventStore = apply(EnvKeys.resourceEventStore)
index eb1c979..4c252e1 100644 (file)
@@ -45,7 +45,7 @@ import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-abstract class ChargingBehavior(val name: String, val inputs: Set[ChargingInput]) {
+abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput]) {
 
   final lazy val inputNames = inputs.map(_.name)
 
@@ -88,7 +88,7 @@ abstract class ChargingBehavior(val name: String, val inputs: Set[ChargingInput]
       unitPrice)
   }
 
-  def isNamed(aName: String): Boolean = aName == name
+  def isNamed(aName: String): Boolean = aName == alias
 
   def needsPreviousEventForCreditAndAmountCalculation: Boolean = {
     // If we need any variable that is related to the previous event
@@ -180,23 +180,6 @@ abstract class ChargingBehavior(val name: String, val inputs: Set[ChargingInput]
 }
 
 object ChargingBehavior {
-  def apply(name: String): ChargingBehavior  = {
-    name match {
-      case null ⇒
-        throw new AquariumException("<null> charging behavior")
-
-      case name ⇒ name.toLowerCase match {
-        case ChargingBehaviorNames.onoff      ⇒ OnOffChargingBehavior
-        case ChargingBehaviorNames.discrete   ⇒ DiscreteChargingBehavior
-        case ChargingBehaviorNames.continuous ⇒ ContinuousChargingBehavior
-        case ChargingBehaviorNames.once       ⇒ ContinuousChargingBehavior
-
-        case _ ⇒
-          throw new AquariumException("Invalid charging behavior %s".format(name))
-      }
-    }
-  }
-
   def makeValueMapFor(
       chargingBehavior: ChargingBehavior,
       totalCredits: Double,
@@ -211,7 +194,7 @@ object ChargingBehavior {
     val inputs = chargingBehavior.inputs
     var map = Map[ChargingInput, Any]()
 
-    if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.name
+    if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.alias
     if(inputs contains TotalCreditsInput  ) map += TotalCreditsInput   -> totalCredits
     if(inputs contains OldTotalAmountInput) map += OldTotalAmountInput -> oldTotalAmount
     if(inputs contains NewTotalAmountInput) map += NewTotalAmountInput -> newTotalAmount
@@ -223,241 +206,3 @@ object ChargingBehavior {
     map
   }
 }
-
-/**
- * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
- *
- * Examples are: a) Give a gift of X credits to the user, b) User bought a book, so charge for the book price.
- *
- */
-case object OnceChargingBehavior
-extends ChargingBehavior(
-    ChargingBehaviorNames.once,
-    Set(ChargingBehaviorNameInput, CurrentValueInput)
-) {
-
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    true
-  }
-
-  def mustGenerateDummyFirstEvent = false // no need to
-
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
-    oldAmount
-  }
-
-  def getResourceInstanceInitialAmount = 0.0
-
-  def supportsImplicitEvents = false
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
-
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
-    throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
-  }
-}
-
-/**
- * In practice a resource usage will be charged for the total amount of usage
- * between resource usage changes.
- *
- * Example resource that might be adept to a continuous policy
- * is diskspace.
- */
-case object ContinuousChargingBehavior
-extends ChargingBehavior(
-    ChargingBehaviorNames.continuous,
-    Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)
-) {
-
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
-    // If the total is in the details, get it, or else compute it
-    details.get("total") match {
-      case Some(total) ⇒
-        total.toDouble
-
-      case _ ⇒
-        oldAmount + newEventValue
-    }
-  }
-
-  def getResourceInstanceInitialAmount: Double = {
-    0.0
-  }
-
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    true
-  }
-
-  def mustGenerateDummyFirstEvent = true
-
-  def supportsImplicitEvents = {
-    true
-  }
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
-    true
-  }
-
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
-    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
-
-    val details = resourceEvent.details
-    val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
-
-    resourceEvent.withDetails(newDetails, newOccurredMillis)
-  }
-}
-
-/**
- * An onoff charging behavior expects a resource to be in one of the two allowed
- * states (`on` and `off`, respectively). It will charge for resource usage
- * within the timeframes specified by consecutive on and off resource events.
- * An onoff policy is the same as a continuous policy, except for
- * the timeframes within the resource is in the `off` state.
- *
- * Example resources that might be adept to onoff policies are VMs in a
- * cloud application and books in a book lending application.
- */
-case object OnOffChargingBehavior
-extends ChargingBehavior(
-    ChargingBehaviorNames.onoff,
-    Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput)
-) {
-
-  /**
-   *
-   * @param oldAmount is ignored
-   * @param newEventValue
-   * @return
-   */
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
-    newEventValue
-  }
-
-  def getResourceInstanceInitialAmount: Double = {
-    0.0
-  }
-
-  private[this]
-  def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
-    import OnOffChargingBehaviorValues.{ON, OFF}
-
-    def exception(rs: OnOffPolicyResourceState) =
-      new AquariumException("Resource state transition error (%s -> %s)".format(rs, rs))
-
-    (oldAmount, newEventValue) match {
-      case (ON, ON) ⇒
-        throw exception(OnResourceState)
-      case (ON, OFF) ⇒
-        OFF
-      case (OFF, ON) ⇒
-        ON
-      case (OFF, OFF) ⇒
-        throw exception(OffResourceState)
-    }
-  }
-
-  override def isBillableEvent(event: ResourceEventModel) = {
-    // ON events do not contribute, only OFF ones.
-    OnOffChargingBehaviorValues.isOFFValue(event.value)
-  }
-
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    false
-  }
-
-  def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs
-
-  def supportsImplicitEvents = {
-    true
-  }
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
-    // If we have ON events with no OFF companions at the end of the billing period,
-    // then we must generate implicit OFF events.
-    OnOffChargingBehaviorValues.isONValue(resourceEvent.value)
-  }
-
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
-    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
-    assert(OnOffChargingBehaviorValues.isONValue(resourceEvent.value))
-
-    val details = resourceEvent.details
-    val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
-    val newValue   = OnOffChargingBehaviorValues.OFF
-
-    resourceEvent.withDetailsAndValue(newDetails, newValue, newOccurredMillis)
-  }
-
-  def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = {
-    throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this))
-  }
-}
-
-object OnOffChargingBehaviorValues {
-  final val ON  = 1.0
-  final val OFF = 0.0
-
-  def isONValue (value: Double) = value == ON
-  def isOFFValue(value: Double) = value == OFF
-}
-
-/**
- * An discrete charging behavior indicates that a resource should be charged directly
- * at each resource state change, i.e. the charging is not dependent on
- * the time the resource.
- *
- * Example oneoff resources might be individual charges applied to various
- * actions (e.g. the fact that a user has created an account) or resources
- * that should be charged per volume once (e.g. the allocation of a volume)
- */
-case object DiscreteChargingBehavior
-extends ChargingBehavior(
-    ChargingBehaviorNames.discrete,
-    Set(ChargingBehaviorNameInput, UnitPriceInput, CurrentValueInput)
-) {
-
-  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
-    oldAmount + newEventValue
-  }
-
-  def getResourceInstanceInitialAmount: Double = {
-    0.0
-  }
-
-  /**
-   * This is called when we have the very first event for a particular resource instance, and we want to know
-   * if it is billable or not.
-   */
-  def isBillableFirstEvent(event: ResourceEventModel) = {
-    false // nope, we definitely need a  previous one.
-  }
-
-  // FIXME: Check semantics of this. I just put false until thorough study
-  def mustGenerateDummyFirstEvent = false
-
-  def supportsImplicitEvents = {
-    false
-  }
-
-  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
-    false
-  }
-
-  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
-    throw new AquariumInternalError("constructImplicitEndEventFor() Not compliant with %s".format(this))
-  }
-}
@@ -40,7 +40,7 @@ package gr.grnet.aquarium.charging
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-object ChargingBehaviorNames {
+object ChargingBehaviorAliases {
   final val onoff      = "onoff"
   final val discrete   = "discrete"
   final val continuous = "continuous"
diff --git a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala
new file mode 100644 (file)
index 0000000..473d88d
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.event.model.resource.ResourceEventModel
+
+/**
+ * In practice a resource usage will be charged for the total amount of usage
+ * between resource usage changes.
+ *
+ * Example resource that might be adept to a continuous policy
+ * is diskspace.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class ContinuousChargingBehavior
+    extends ChargingBehavior(
+      ChargingBehaviorAliases.continuous,
+      Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
+
+  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+    // If the total is in the details, get it, or else compute it
+    details.get("total") match {
+      case Some(total) ⇒
+        total.toDouble
+
+      case _ ⇒
+        oldAmount + newEventValue
+    }
+  }
+
+  def getResourceInstanceInitialAmount: Double = {
+    0.0
+  }
+
+  /**
+   * This is called when we have the very first event for a particular resource instance, and we want to know
+   * if it is billable or not.
+   */
+  def isBillableFirstEvent(event: ResourceEventModel) = {
+    true
+  }
+
+  def mustGenerateDummyFirstEvent = true
+
+  def supportsImplicitEvents = {
+    true
+  }
+
+  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+    true
+  }
+
+  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
+    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
+
+    val details = resourceEvent.details
+    val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
+
+    resourceEvent.withDetails(newDetails, newOccurredMillis)
+  }
+}
+
+object ContinuousChargingBehavior {
+  private[this] final val TheOne = new ContinuousChargingBehavior
+
+  def apply(): ContinuousChargingBehavior = TheOne
+}
diff --git a/src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala
new file mode 100644 (file)
index 0000000..178237f
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.AquariumInternalError
+
+/**
+ * An discrete charging behavior indicates that a resource should be charged directly
+ * at each resource state change, i.e. the charging is not dependent on
+ * the time the resource.
+ *
+ * Example oneoff resources might be individual charges applied to various
+ * actions (e.g. the fact that a user has created an account) or resources
+ * that should be charged per volume once (e.g. the allocation of a volume)
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class DiscreteChargingBehavior
+    extends ChargingBehavior(
+      ChargingBehaviorAliases.discrete,
+      Set(ChargingBehaviorNameInput, UnitPriceInput, CurrentValueInput)) {
+
+  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+    oldAmount + newEventValue
+  }
+
+  def getResourceInstanceInitialAmount: Double = {
+    0.0
+  }
+
+  /**
+   * This is called when we have the very first event for a particular resource instance, and we want to know
+   * if it is billable or not.
+   */
+  def isBillableFirstEvent(event: ResourceEventModel) = {
+    false // nope, we definitely need a  previous one.
+  }
+
+  // FIXME: Check semantics of this. I just put false until thorough study
+  def mustGenerateDummyFirstEvent = false
+
+  def supportsImplicitEvents = {
+    false
+  }
+
+  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+    false
+  }
+
+  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
+    throw new AquariumInternalError("constructImplicitEndEventFor() Not compliant with %s".format(this))
+  }
+}
+
+object DiscreteChargingBehavior {
+  private[this] final val TheOne = new DiscreteChargingBehavior
+
+  def apply(): DiscreteChargingBehavior = TheOne
+}
diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehavior.scala
new file mode 100644 (file)
index 0000000..e84e269
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.{AquariumInternalError, AquariumException}
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+
+/**
+ * An onoff charging behavior expects a resource to be in one of the two allowed
+ * states (`on` and `off`, respectively). It will charge for resource usage
+ * within the timeframes specified by consecutive on and off resource events.
+ * An onoff policy is the same as a continuous policy, except for
+ * the timeframes within the resource is in the `off` state.
+ *
+ * Example resources that might be adept to onoff policies are VMs in a
+ * cloud application and books in a book lending application.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class OnOffChargingBehavior
+    extends ChargingBehavior(
+      ChargingBehaviorAliases.onoff,
+      Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput)) {
+
+  /**
+   *
+   * @param oldAmount is ignored
+   * @param newEventValue
+   * @return
+   */
+  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+    newEventValue
+  }
+
+  def getResourceInstanceInitialAmount: Double = {
+    0.0
+  }
+
+  private[this]
+  def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
+    import OnOffChargingBehaviorValues.{ON, OFF}
+
+    def exception(rs: OnOffPolicyResourceState) =
+      new AquariumException("Resource state transition error (%s -> %s)".format(rs, rs))
+
+    (oldAmount, newEventValue) match {
+      case (ON, ON) ⇒
+        throw exception(OnResourceState)
+      case (ON, OFF) ⇒
+        OFF
+      case (OFF, ON) ⇒
+        ON
+      case (OFF, OFF) ⇒
+        throw exception(OffResourceState)
+    }
+  }
+
+  override def isBillableEvent(event: ResourceEventModel) = {
+    // ON events do not contribute, only OFF ones.
+    OnOffChargingBehaviorValues.isOFFValue(event.value)
+  }
+
+  /**
+   * This is called when we have the very first event for a particular resource instance, and we want to know
+   * if it is billable or not.
+   */
+  def isBillableFirstEvent(event: ResourceEventModel) = {
+    false
+  }
+
+  def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs
+
+  def supportsImplicitEvents = {
+    true
+  }
+
+  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+    // If we have ON events with no OFF companions at the end of the billing period,
+    // then we must generate implicit OFF events.
+    OnOffChargingBehaviorValues.isONValue(resourceEvent.value)
+  }
+
+  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
+    assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
+    assert(OnOffChargingBehaviorValues.isONValue(resourceEvent.value))
+
+    val details = resourceEvent.details
+    val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
+    val newValue   = OnOffChargingBehaviorValues.OFF
+
+    resourceEvent.withDetailsAndValue(newDetails, newValue, newOccurredMillis)
+  }
+
+  def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = {
+    throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this))
+  }
+}
+
+object OnOffChargingBehavior {
+  private[this] final val TheOne = new OnOffChargingBehavior
+
+  def apply(): OnOffChargingBehavior = TheOne
+}
@@ -40,7 +40,11 @@ package gr.grnet.aquarium.charging
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-object OnOffPolicyResourceStateNames {
-  final val on  = "on"
-  final val off = "off"
+object OnOffChargingBehaviorValues {
+  final val ON  = 1.0
+  final val OFF = 0.0
+
+  def isONValue (value: Double) = value == ON
+  def isOFFValue(value: Double) = value == OFF
 }
+
index 4e3d3a1..2ae9a96 100644 (file)
@@ -49,25 +49,18 @@ sealed abstract class OnOffPolicyResourceState(val state: String) {
   def isOff: Boolean = !isOn
 }
 
-object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.on) {
+object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.on) {
   override def isOn = true
 }
 
-object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.off) {
+object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.off) {
   override def isOff = true
 }
 
 object OnOffPolicyResourceState {
-  def apply(name: Any): OnOffPolicyResourceState = {
-    name match {
-      case x: String if (x.equalsIgnoreCase(OnOffPolicyResourceStateNames.on))  ⇒ OnResourceState
-      case y: String if (y.equalsIgnoreCase(OnOffPolicyResourceStateNames.off)) ⇒ OffResourceState
-      case a: Double if (a == 0) => OffResourceState
-      case b: Double if (b == 1) => OnResourceState
-      case i: Int if (i == 0) => OffResourceState
-      case j: Int if (j == 1) => OnResourceState
-      case _ => throw new AquariumException("Invalid OnOffPolicyResourceState %s".format(name))
-    }
+  object Names {
+    final val on  = "on"
+    final val off = "off"
   }
 }
 
diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala
new file mode 100644 (file)
index 0000000..1a1213d
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.AquariumException
+
+/**
+ * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
+ *
+ * Examples are: a) Give a gift of X credits to the user, b) User bought a book, so charge for the book price.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class OnceChargingBehavior
+    extends ChargingBehavior(
+      ChargingBehaviorAliases.once,
+      Set(ChargingBehaviorNameInput, CurrentValueInput)) {
+
+  /**
+   * This is called when we have the very first event for a particular resource instance, and we want to know
+   * if it is billable or not.
+   */
+  def isBillableFirstEvent(event: ResourceEventModel) = {
+    true
+  }
+
+  def mustGenerateDummyFirstEvent = false // no need to
+
+  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
+    oldAmount
+  }
+
+  def getResourceInstanceInitialAmount = 0.0
+
+  def supportsImplicitEvents = false
+
+  def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
+
+  def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
+    throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
+  }
+}
+
+object OnceChargingBehavior {
+  private[this] final val TheOne = new OnceChargingBehavior
+
+  def apply(): OnceChargingBehavior = TheOne
+}
index d5cabe1..09714b3 100644 (file)
@@ -40,7 +40,7 @@ import com.ckkloverdos.maybe.{NoVal, Maybe}
 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
 import gr.grnet.aquarium.store.PolicyStore
 import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.logic.accounting.algorithm.SimpleExecutableChargingBehaviorAlgorithm
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
@@ -170,6 +170,7 @@ trait TimeslotComputations extends Loggable {
    *
    */
   def computeFullChargeslots(
+      aquarium: Aquarium,
       previousResourceEventOpt: Option[ResourceEventModel],
       currentResourceEvent: ResourceEventModel,
       oldCredits: Double,
@@ -186,7 +187,7 @@ trait TimeslotComputations extends Loggable {
 
     val occurredDate = currentResourceEvent.occurredDate
     val occurredMillis = currentResourceEvent.occurredMillis
-    val chargingBehavior = resourceType.chargingBehavior
+    val chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
 
     val (referenceTimeslot, policyByTimeslot, previousValue) = chargingBehavior.needsPreviousEventForCreditAndAmountCalculation match {
       // We need a previous event
index b2e8677..6d7cb76 100644 (file)
@@ -203,7 +203,7 @@ final class UserStateComputations extends AquariumAwareSkeleton with Loggable {
     resourceTypesMap.get(theResource) match {
       // We have a resource type (and thus a charging behavior)
       case Some(resourceType) ⇒
-        val chargingBehavior = resourceType.chargingBehavior
+        val chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
         clog.debug("%s for %s", chargingBehavior, resourceType)
         val isBillable = chargingBehavior.isBillableEvent(currentResourceEvent)
         if(!isBillable) {
@@ -265,6 +265,7 @@ final class UserStateComputations extends AquariumAwareSkeleton with Loggable {
 
             //              clog.debug("Computing full chargeslots")
             val (referenceTimeslot, fullChargeslots) = timeslotComputations.computeFullChargeslots(
+              aquarium,
               previousResourceEventOpt1,
               currentResourceEvent,
               oldCredits,
@@ -469,7 +470,7 @@ final class UserStateComputations extends AquariumAwareSkeleton with Loggable {
       // Second, for the remaining events which must contribute an implicit OFF, we collect those OFFs
       // ... in order to generate an implicit ON later (during the next billing cycle).
       val (specialEvents, theirImplicitEnds) = userStateWorker.
-        findAndRemoveGeneratorsOfImplicitEndEvents(billingMonthInfo.monthStopMillis)
+        findAndRemoveGeneratorsOfImplicitEndEvents(aquarium, billingMonthInfo.monthStopMillis)
 
       if(specialEvents.lengthCompare(1) >= 0 || theirImplicitEnds.lengthCompare(1) >= 0) {
         clog.debug("")
index bb0692f..40c2e71 100644 (file)
@@ -41,6 +41,7 @@ import gr.grnet.aquarium.util.ContextualLogger
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.computation.state.parts.{IgnoredFirstResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, LatestResourceEventsWorker}
 import gr.grnet.aquarium.policy.ResourceType
+import gr.grnet.aquarium.Aquarium
 
 /**
  * A helper object holding intermediate state/results during resource event processing.
@@ -144,6 +145,7 @@ case class UserStateWorker(
    * @see [[gr.grnet.aquarium.charging.ChargingBehavior]]
    */
   def findAndRemoveGeneratorsOfImplicitEndEvents(
+      aquarium: Aquarium,
       /**
        * The `occurredMillis` that will be recorded in the synthetic implicit OFFs.
        * Normally, this will be the end of a billing month.
@@ -159,7 +161,7 @@ case class UserStateWorker(
       for {
         resourceEvent ← resourceEvents
         resourceType ← resourceTypesMap.get(resourceEvent.safeResource)
-        chargingBehavior = resourceType.chargingBehavior
+        chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
       } {
         if(chargingBehavior.supportsImplicitEvents) {
           if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) {
 package gr.grnet.aquarium.logic.accounting.algorithm
 
 
-import com.ckkloverdos.maybe.Maybe
-import gr.grnet.aquarium.logic.accounting.dsl._
 import gr.grnet.aquarium.AquariumException
-import gr.grnet.aquarium.charging.{CurrentValueInput, TimeDeltaInput, OldTotalAmountInput, UnitPriceInput, ChargingBehaviorNames, ChargingBehaviorNameInput, ChargingInput}
+import gr.grnet.aquarium.charging.{CurrentValueInput, TimeDeltaInput, OldTotalAmountInput, UnitPriceInput, ChargingBehaviorAliases, ChargingBehaviorNameInput, ChargingInput}
 
 /**
  * An executable charging algorithm with some simple implementation.
@@ -53,26 +51,26 @@ object SimpleExecutableChargingBehaviorAlgorithm extends ExecutableChargingBehav
 
   def apply(vars: Map[ChargingInput, Any]): Double = {
     vars.apply(ChargingBehaviorNameInput) match {
-      case ChargingBehaviorNames.continuous ⇒
+      case ChargingBehaviorAliases.continuous ⇒
         val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
         val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
         val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
 
         hrs(timeDelta) * oldTotalAmount * unitPrice
 
-      case ChargingBehaviorNames.discrete ⇒
+      case ChargingBehaviorAliases.discrete ⇒
         val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
         val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
 
         currentValue * unitPrice
 
-      case ChargingBehaviorNames.onoff ⇒
+      case ChargingBehaviorAliases.onoff ⇒
         val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
         val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
 
         hrs(timeDelta) * unitPrice
 
-      case ChargingBehaviorNames.once ⇒
+      case ChargingBehaviorAliases.once ⇒
         val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
         currentValue
 
@@ -83,8 +81,8 @@ object SimpleExecutableChargingBehaviorAlgorithm extends ExecutableChargingBehav
 
   override def toString = "SimpleExecutableCostPolicyAlgorithm(%s)".format(
     Map(
-      ChargingBehaviorNames.continuous -> "hrs(timeDelta) * oldTotalAmount * unitPrice",
-      ChargingBehaviorNames.discrete   -> "currentValue * unitPrice",
-      ChargingBehaviorNames.onoff      -> "hrs(timeDelta) * unitPrice",
-      ChargingBehaviorNames.once       -> "currentValue"))
+      ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * unitPrice",
+      ChargingBehaviorAliases.discrete   -> "currentValue * unitPrice",
+      ChargingBehaviorAliases.onoff      -> "hrs(timeDelta) * unitPrice",
+      ChargingBehaviorAliases.once       -> "currentValue"))
 }
index 6de41bb..b0f7ac7 100644 (file)
@@ -89,6 +89,6 @@ case class EffectiveUnitPrice(unitPrice: Double, when: Option[(CronSpec,CronSpec
     case None => "? ? ? ? ?"
     case Some((_,s)) => s.toString
   }
-  override def toString : String = "EffectiveUnitPrice(%d,%s,%s)".
+  override def toString : String = "EffectiveUnitPrice(%f,%s,%s)".
                           format(unitPrice,stringOfStartCron,stringOfEndCron)
  }
index 7e6fa50..79905c1 100644 (file)
@@ -77,7 +77,7 @@ trait PolicyModel extends JsonSupport {
    * Note than since a charging behavior is semantically attached to an implementation, a change in the set
    * of known charging behaviors normally means a change in the implementation of Aquarium.
    */
-  def chargingBehaviorClasses: Set[String/*ImplementationClassName*/]
+  def chargingBehaviors: Set[String/*ImplementationClassName*/]
 
   /**
    * Each role is mapped to a full price table.
index d01da5e..7545903 100644 (file)
 
 package gr.grnet.aquarium.policy
 
-import gr.grnet.aquarium.charging.ChargingBehavior
 
 /**
  * The definition of a resource type. A resource type is a broad classification to which resource instances belong.
  *
  * @param name The name of this resource type. Examples are `vmtime`, `diskspace`, `bandwidth`, `diskio`.
  * @param unit The unit we use to count the resource usage.
- * @param chargingBehavior The charging behavior used for this resource types.
+ * @param chargingBehavior The fully qualified name of the [[gr.grnet.aquarium.charging.ChargingBehavior]]
+ *                         implementation used for this resource type.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-case class ResourceType(name: String, unit: String, chargingBehavior: ChargingBehavior)
+case class ResourceType(name: String, unit: String, chargingBehavior: String)
index ce3239a..0b0667b 100644 (file)
@@ -48,7 +48,7 @@ case class StdPolicy(
     parentID: Option[String],
     validityTimespan: Timespan,
     resourceTypes: Set[ResourceType],
-    chargingBehaviorClasses: Set[String],
+    chargingBehaviors: Set[String],
     roleMapping: Map[String/*Role*/, FullPriceTable]
 ) extends PolicyModel {
 
index 1b86d54..bcd574f 100644 (file)
@@ -223,7 +223,7 @@ final class AkkaService extends AquariumAwareSkeleton with Configurable with Lif
         // Create new User Actor instance
         logger.debug("Creating new UserActor instance for %s".format(userID))
         val actorRef = _actorSystem.actorOf(Props.apply({
-          aquarium.newInstance(classOf[UserActor], classOf[UserActor].getName)
+          aquarium.newInstance(classOf[UserActor])
         }), "userActor::%s".format(userID))
 
         // Cache it for subsequent calls
index f9bf56d..abf2f34 100644 (file)
@@ -46,7 +46,7 @@ import gr.grnet.aquarium.charging.ChargingBehavior
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-class ResourceSim(val name: String, val unit: String, val chargingBehavior: ChargingBehavior ) {
+class ResourceSim(val name: String, val unit: String, val chargingBehavior: String) {
 
   def toResourceType = ResourceType(name, unit, chargingBehavior)
 
@@ -61,7 +61,7 @@ class ResourceSim(val name: String, val unit: String, val chargingBehavior: Char
 
 object ResourceSim {
   def apply(name: String, unit: String, chargingBehavior: ChargingBehavior) = {
-    new ResourceSim(name, unit, chargingBehavior)
+    new ResourceSim(name, unit, chargingBehavior.getClass.getName)
   }
 
 }
index c722649..117af66 100644 (file)
@@ -47,8 +47,8 @@ import gr.grnet.aquarium.charging.{DiscreteChargingBehavior, ChargingBehavior}
 class StdBandwidthResourceSim(
     name: String = StdVMTimeResourceSim.DSLNames.name,
     unit: String = StdVMTimeResourceSim.DSLNames.unit,
-    costPolicy: ChargingBehavior = DiscreteChargingBehavior
-) extends ResourceSim(name, unit, costPolicy) {
+    chargingBehavior: String = classOf[DiscreteChargingBehavior].getName
+) extends ResourceSim(name, unit, chargingBehavior) {
 
 override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
     StdBandwidthInstanceSim(this, instanceId, owner, client)
index 3abe3e8..97d4813 100644 (file)
@@ -47,7 +47,7 @@ import gr.grnet.aquarium.charging.{ContinuousChargingBehavior, ChargingBehavior}
 class StdDiskspaceResourceSim(
     name: String = StdVMTimeResourceSim.DSLNames.name,
     unit: String = StdVMTimeResourceSim.DSLNames.unit,
-    chargingBehavior: ChargingBehavior = ContinuousChargingBehavior
+    chargingBehavior: String = classOf[ContinuousChargingBehavior].getName
 ) extends ResourceSim(name, unit, chargingBehavior) {
 
   override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
index d15e18b..a65537b 100644 (file)
@@ -48,7 +48,7 @@ import gr.grnet.aquarium.charging.{OnOffChargingBehavior, ChargingBehavior}
 class StdVMTimeResourceSim(
     name: String = StdVMTimeResourceSim.DSLNames.name,
     unit: String = StdVMTimeResourceSim.DSLNames.unit,
-    chargingBehavior: ChargingBehavior = OnOffChargingBehavior
+    chargingBehavior: String = classOf[OnOffChargingBehavior].getName
 ) extends ResourceSim(name, unit, chargingBehavior) {
 
   override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
index f58fe3d..e513606 100644 (file)
@@ -279,7 +279,7 @@ extends StoreProvider
       parentID = policy.parentID,
       validityTimespan = policy.validityTimespan,
       resourceTypes = policy.resourceTypes,
-      chargingBehaviorClasses = policy.chargingBehaviorClasses,
+      chargingBehaviors = policy.chargingBehaviors,
       roleMapping = policy.roleMapping
     )
     _policies = localPolicy :: _policies
index bf7150b..576ef86 100644 (file)
@@ -45,14 +45,14 @@ import gr.grnet.aquarium.policy.{PolicyModel, FullPriceTable, ResourceType}
 
 case class MongoDBPolicy(
     _id: String,
-    id: String,
     parentID: Option[String],
     validityTimespan: Timespan,
     resourceTypes: Set[ResourceType],
-    chargingBehaviorClasses: Set[String],
+    chargingBehaviors: Set[String],
     roleMapping: Map[String/*Role*/, FullPriceTable]
 ) extends PolicyModel {
 
+  def id = _id
   def idInStore = Some(_id)
 }
 
@@ -60,11 +60,10 @@ object MongoDBPolicy {
   final def fromOther(policy: PolicyModel, _id: String): MongoDBPolicy = {
     MongoDBPolicy(
       _id,
-      policy.id,
       policy.parentID,
       policy.validityTimespan,
       policy.resourceTypes,
-      policy.chargingBehaviorClasses,
+      policy.chargingBehaviors,
       policy.roleMapping
     )
   }
index 0f22897..00b1f05 100644 (file)
@@ -59,7 +59,7 @@ mongodb.connection.pool.size=20
 
 # Relative to AQUARIUM_HOME or an absolute path
 # DO NOT set this in production
-#events.store.folder=../events-store
+events.store.folder=../events-store
 
 # Store resource events to events.store.folder as well
 events.store.save.rc.events=false
@@ -83,5 +83,8 @@ store.provider.class=gr.grnet.aquarium.store.mongodb.MongoDBStoreProvider
 # Override the user event store (if present, it will not be given by the store provider above)
 #policy.store.class=
 
+# A time period in milliseconds for which we can tolerate stale parts regarding user state.
+user.state.timestamp.threshold=1
+
 # Administrative REST API authorization cookie
 admin.cookie=1
\ No newline at end of file
index 9fe7030..ab5df19 100644 (file)
@@ -35,8 +35,7 @@
 package gr.grnet.aquarium.logic.test
 
 import org.junit.Test
-import gr.grnet.aquarium.logic.accounting.dsl.{Timeslot, DSLTimeFrameRepeat,DSLTimeFrame}
-import gr.grnet.aquarium.util.TestMethods
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 import java.util.Date
 import scala._
 import scala.Some
index 9a259f9..a9d95dd 100644 (file)
@@ -35,7 +35,6 @@
 
 package gr.grnet.aquarium.logic.test
 
-import gr.grnet.aquarium.util.TestMethods
 import org.junit.Assert._
 import org.junit.{Test}
 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
@@ -47,7 +46,7 @@ import gr.grnet.aquarium.util.date.MutableDateCalc
  *
  * @author Georgios Gousios <gousiosg@gmail.com>
  */
-class TimeslotTest extends TestMethods {
+class TimeslotTest /*extends TestMethods*/ {
 
   @Test
   def testOverlappingTimeslots = {
diff --git a/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala b/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala
new file mode 100644 (file)
index 0000000..f320cbf
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.policy
+
+import org.junit.Test
+import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.charging.{OnceChargingBehavior, ContinuousChargingBehavior, OnOffChargingBehavior, DiscreteChargingBehavior}
+import gr.grnet.aquarium.converter.{StdConverters, PrettyJsonTextFormat}
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+class StdPolicyTest {
+  final lazy val policy = StdPolicy(
+    id = "policy-1",
+    parentID = None,
+
+    validityTimespan = Timespan(0),
+
+    resourceTypes = Set(
+      ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+      ResourceType("vmtime",    "Hr",    classOf[OnOffChargingBehavior].getName),
+      ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
+    ),
+
+    chargingBehaviors = Set(
+      classOf[DiscreteChargingBehavior].getName,
+      classOf[OnOffChargingBehavior].getName,
+      classOf[ContinuousChargingBehavior].getName,
+      classOf[OnceChargingBehavior].getName
+    ),
+
+    roleMapping = Map(
+      "default" -> FullPriceTable(Map(
+        "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+        "vmtime"    -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+        "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
+      ))
+    )
+  )
+
+  @Test
+  def testJson(): Unit = {
+    val converters = StdConverters.AllConverters
+    val json = converters.convertEx[PrettyJsonTextFormat](policy)
+    val obj = converters.convertEx[StdPolicy](json)
+
+    assert(policy == obj)
+  }
+}
index 7fe30b2..24f0dd0 100644 (file)
 package gr.grnet.aquarium.user
 
 import gr.grnet.aquarium.store.memory.MemStoreProvider
-import gr.grnet.aquarium.logic.accounting.dsl._
 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.{Timespan, Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
+import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
 import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, MonthlyBillingCalculation}
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.computation.state.{UserStateBootstrap, UserState}
+import gr.grnet.aquarium.computation.state.UserState
 import gr.grnet.aquarium.charging._
 import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy, PolicyModel}
 import gr.grnet.aquarium.Timespan
-import scala.Some
 import gr.grnet.aquarium.computation.state.UserStateBootstrap
-import gr.grnet.aquarium.simulation.AquariumSim
-import gr.grnet.aquarium.simulation.ClientSim
 
 
 /**
@@ -76,14 +72,15 @@ class UserStateComputationsTest extends Loggable {
     parentID = None,
     validityTimespan = Timespan(0),
     resourceTypes = Set(
-      ResourceType("bandwidth", "MB/Hr", DiscreteChargingBehavior),
-      ResourceType("vmtime",    "Hr",    OnOffChargingBehavior),
-      ResourceType("diskspace", "MB/Hr", ContinuousChargingBehavior)
+      ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+      ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+      ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
     ),
-    chargingBehaviorClasses = Set(
-      DiscreteChargingBehavior.getClass.getName,
-      OnOffChargingBehavior.getClass.getName,
-      ContinuousChargingBehavior.getClass.getName
+    chargingBehaviors = Set(
+      classOf[DiscreteChargingBehavior].getName,
+      classOf[OnOffChargingBehavior].getName,
+      classOf[ContinuousChargingBehavior].getName,
+      classOf[OnceChargingBehavior].getName
     ),
     roleMapping = Map(
       "default" -> FullPriceTable(Map(
@@ -123,7 +120,7 @@ class UserStateComputationsTest extends Loggable {
 
     def apply(vars: Map[ChargingInput, Any]): Double = {
       vars.apply(ChargingBehaviorNameInput) match {
-        case ChargingBehaviorNames.continuous ⇒
+        case ChargingBehaviorAliases.continuous ⇒
           val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
           val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
           val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
@@ -132,7 +129,7 @@ class UserStateComputationsTest extends Loggable {
 
           creditsForContinuous(timeDelta, oldTotalAmount)
 
-        case ChargingBehaviorNames.discrete ⇒
+        case ChargingBehaviorAliases.discrete ⇒
           val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
           val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
 
@@ -140,7 +137,7 @@ class UserStateComputationsTest extends Loggable {
 
           creditsForDiscrete(currentValue)
 
-        case ChargingBehaviorNames.onoff ⇒
+        case ChargingBehaviorAliases.onoff ⇒
           val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
           val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
 
@@ -148,7 +145,7 @@ class UserStateComputationsTest extends Loggable {
 
           creditsForOnOff(timeDelta)
 
-        case ChargingBehaviorNames.once ⇒
+        case ChargingBehaviorAliases.once ⇒
           val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
           currentValue
 
@@ -159,10 +156,10 @@ class UserStateComputationsTest extends Loggable {
 
     override def toString = "DefaultAlgorithm(%s)".format(
       Map(
-        ChargingBehaviorNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
-        ChargingBehaviorNames.discrete   -> "currentValue * %s".format(DiscreteUnitPrice),
-        ChargingBehaviorNames.onoff      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
-        ChargingBehaviorNames.once       -> "currentValue"))
+        ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
+        ChargingBehaviorAliases.discrete   -> "currentValue * %s".format(DiscreteUnitPrice),
+        ChargingBehaviorAliases.onoff      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
+        ChargingBehaviorAliases.once       -> "currentValue"))
   }
 
   val DefaultCompiler  = new CostPolicyAlgorithmCompiler {