Merge branch 'master' of https://code.grnet.gr/git/aquarium
authorProdromos Gerakios <pgerakios@grnet.gr>
Fri, 17 Aug 2012 05:48:42 +0000 (08:48 +0300)
committerProdromos Gerakios <pgerakios@grnet.gr>
Fri, 17 Aug 2012 05:48:42 +0000 (08:48 +0300)
28 files changed:
pom.xml
src/main/resources/policy.json
src/main/resources/policy.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/ContinuousChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala [deleted file]
src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala
src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala
src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala [moved from src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehavior.scala with 69% similarity]
src/main/scala/gr/grnet/aquarium/charging/VMChargingBehaviorValues.scala [moved from src/main/scala/gr/grnet/aquarium/charging/OnOffChargingBehaviorValues.scala with 86% similarity]
src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala
src/main/scala/gr/grnet/aquarium/converter/JsonConversions.scala
src/main/scala/gr/grnet/aquarium/converter/StdConverters.scala
src/main/scala/gr/grnet/aquarium/event/model/resource/ResourceEventModel.scala
src/main/scala/gr/grnet/aquarium/policy/EffectiveUnitPrice.scala
src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala
src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala
src/main/scala/gr/grnet/aquarium/policy/UserAgreementModel.scala
src/main/scala/gr/grnet/aquarium/simulation/StdBandwidthResourceSim.scala [deleted file]
src/main/scala/gr/grnet/aquarium/simulation/StdVMTimeInstanceSim.scala
src/main/scala/gr/grnet/aquarium/simulation/StdVMTimeResourceSim.scala
src/main/scala/gr/grnet/aquarium/util/package.scala
src/test/resources/logback-test.xml
src/test/resources/policy.json
src/test/scala/gr/grnet/aquarium/event/model/StdResourceEventTest.scala [moved from src/main/scala/gr/grnet/aquarium/simulation/StdBandwidthInstanceSim.scala with 66% similarity]
src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala
src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala

diff --git a/pom.xml b/pom.xml
index f2cc62c..27c88f6 100644 (file)
--- a/pom.xml
+++ b/pom.xml
       <plugin>
         <groupId>net.alchim31.maven</groupId>
         <artifactId>scala-maven-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.1.0</version>
         <configuration>
+          <recompileMode>incremental</recompileMode>
+
           <charset>${project.build.sourceEncoding}</charset>
           <jvmArgs>
             <jvmArg>-Xmx1024m</jvmArg>
index bbf452b..86b4895 100644 (file)
@@ -1,71 +1,67 @@
-{
-  "id":"750E6309-AB60-41B4-8D4B-9FFEA6EF843C",
+{ "jsonClass":"gr.grnet.aquarium.policy.StdPolicy",
+
+  "id":"default-policy",
 
   "validityTimespan":{
     "fromMillis":0,
     "toMillis":9223372036854775807
   },
 
-  "resourceTypes":[
-    {
-      "name":"addcredits",
-      "unit":"credits",
-      "chargingBehavior":"gr.grnet.aquarium.charging.OnceChargingBehavior"
-    },
-    {
-      "name":"bandwidth",
-      "unit":"MB/Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.DiscreteChargingBehavior"
-    },
-    {
-      "name":"vmtime",
-      "unit":"Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.OnOffChargingBehavior"
-    },
-    {
-      "name":"diskspace",
-      "unit":"MB/Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
-    }
-  ],
+  "resourceTypes":[{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"diskspace",
+    "unit":"MB/Hr",
+    "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
+  },{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"vmtime",
+    "unit":"Hr",
+    "chargingBehavior":"gr.grnet.aquarium.charging.VMChargingBehavior"
+  },{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"addcredits",
+    "unit":"credits",
+    "chargingBehavior":"gr.grnet.aquarium.charging.OnceChargingBehavior"
+    }],
 
   "chargingBehaviors":[
-    "gr.grnet.aquarium.charging.DiscreteChargingBehavior",
-    "gr.grnet.aquarium.charging.OnOffChargingBehavior",
+    "gr.grnet.aquarium.charging.VMChargingBehavior",
     "gr.grnet.aquarium.charging.ContinuousChargingBehavior",
     "gr.grnet.aquarium.charging.OnceChargingBehavior"
   ],
 
   "roleMapping":{
     "default":{
+      "jsonClass":"gr.grnet.aquarium.policy.FullPriceTable",
+
       "perResource":{
-        "bandwidth":{
-          "priceOverrides":[
-            {
+        "diskspace":{
+          "default":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
               "unitPrice":0.01
-            }
-          ]
+            }]
+          }
         },
+
         "vmtime":{
-          "priceOverrides":[
-            {
+          "powerOn":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
               "unitPrice":0.01
-            }
-          ]
+            }]
+          },
+          "powerOff":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
+              "unitPrice":0.0010
+            }]
+          }
         },
-        "diskspace":{
-          "priceOverrides":[
-            {
-              "unitPrice":0.01
-            }
-          ]
-        }
+
         "addcredits":{
-          "priceOverrides":[
-            {
-              "unitPrice":1
-            }
-          ]
+          "default":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
+              "unitPrice":-1.0
+            }]
+          }
         }
       }
     }
index 0657131..b96a988 100644 (file)
@@ -1,6 +1,9 @@
-import gr.grnet.aquarium.charging.{OnceChargingBehavior, ContinuousChargingBehavior, OnOffChargingBehavior, DiscreteChargingBehavior}
+import gr.grnet.aquarium.charging.VMChargingBehavior.Selectors.Power
+import gr.grnet.aquarium.charging.{OnceChargingBehavior, ContinuousChargingBehavior, VMChargingBehavior}
+import gr.grnet.aquarium.policy.FullPriceTable._
 import gr.grnet.aquarium.policy.{EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy}
 import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.util.nameOfClass
 
 // Definition of our standard policy in plain Scala
 
@@ -11,23 +14,25 @@ StdPolicy(
   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)
+    ResourceType("vmtime", "Hr", nameOfClass[VMChargingBehavior]),
+    ResourceType("diskspace", "MB/Hr", nameOfClass[ContinuousChargingBehavior])
   ),
 
   chargingBehaviors = Set(
-    classOf[DiscreteChargingBehavior].getName,
-    classOf[OnOffChargingBehavior].getName,
-    classOf[ContinuousChargingBehavior].getName,
-    classOf[OnceChargingBehavior].getName
+    nameOfClass[VMChargingBehavior],
+    nameOfClass[ContinuousChargingBehavior],
+    nameOfClass[OnceChargingBehavior]
   ),
 
   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)
+      "diskspace" -> Map(
+        DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)
+      ),
+      "vmtime" -> Map(
+        Power.powerOn  -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil),
+        Power.powerOff -> EffectivePriceTable(EffectiveUnitPrice(0.001) :: Nil) // cheaper when the VM is OFF
+      )
     ))
   )
 )
\ No newline at end of file
index 70cb330..99e9d39 100644 (file)
@@ -40,7 +40,7 @@ import scala.collection.mutable
 
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.{Aquarium, AquariumInternalError, AquariumException}
-import gr.grnet.aquarium.policy.{UserAgreementModel, ResourceType}
+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
@@ -58,9 +58,12 @@ import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput]) extends Loggable {
+abstract class ChargingBehavior(
+    final val alias: String,
+    final val inputs: Set[ChargingInput],
+    final val selectorHierarchy: List[List[String]] = Nil) extends Loggable {
 
-  final lazy val inputNames = inputs.map(_.name)
+  final val inputNames = inputs.map(_.name)
 
   @inline private[this] def hrs(millis: Double) = {
     val hours = millis / 1000 / 60 / 60
@@ -95,7 +98,7 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
 
        (credits, explanation)
 
-     case ChargingBehaviorAliases.onoff ⇒
+     case ChargingBehaviorAliases.vmtime ⇒
        val credits = hrs(timeDeltaMillis) * unitPrice
        val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
 
@@ -116,6 +119,16 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
     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
@@ -164,11 +177,25 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
       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
+      agreementByTimeslot,
+      effectivePriceTableSelector
     )
 
     val fullChargeslots = initialChargeslots.map {
@@ -244,6 +271,30 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
     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)
+  }
+
   /**
    *
    * @param aquarium
@@ -443,7 +494,7 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
    * 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.OnOffChargingBehavior]].
+   * The only exception to the rule is ON events for [[gr.grnet.aquarium.charging.VMChargingBehavior]].
    */
   def isBillableEvent(event: ResourceEventModel): Boolean = true
 
@@ -476,7 +527,7 @@ abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput
    * 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.OnOffChargingBehavior]] for which we
+   * 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.
    *
index 6699d8e..5d5c451 100644 (file)
@@ -41,7 +41,7 @@ package gr.grnet.aquarium.charging
  */
 
 object ChargingBehaviorAliases {
-  final val onoff      = "onoff"
+  final val vmtime     = "vmtime"
   final val discrete   = "discrete"
   final val continuous = "continuous"
   final val once       = "once"
index 473d88d..7241a77 100644 (file)
 package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import scala.collection.mutable
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
 
 /**
  * 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.
+ * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
@@ -51,6 +52,18 @@ final class ContinuousChargingBehavior
       ChargingBehaviorAliases.continuous,
       Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
 
+  protected def computeSelectorPath(
+      chargingData: mutable.Map[String, Any],
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      previousValue: Double,
+      totalCredits: Double,
+      oldAccumulatingAmount: Double,
+      newAccumulatingAmount: Double
+  ): List[String] = {
+    Nil
+  }
+
   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 {
diff --git a/src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/DiscreteChargingBehavior.scala
deleted file mode 100644 (file)
index 178237f..0000000
+++ /dev/null
@@ -1,93 +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.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
-}
index 2ae9a96..f3573ca 100644 (file)
@@ -39,7 +39,7 @@ import gr.grnet.aquarium.AquariumException
 
 /**
  * Encapsulates the possible states that a resource with an
- * [[gr.grnet.aquarium.charging.OnOffChargingBehavior]]
+ * [[gr.grnet.aquarium.charging.VMChargingBehavior]]
  * can be.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
index 1a1213d..bc8327c 100644 (file)
@@ -37,6 +37,9 @@ package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 import gr.grnet.aquarium.AquariumException
+import scala.collection.mutable
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.policy.FullPriceTable
 
 /**
  * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
@@ -50,6 +53,17 @@ final class OnceChargingBehavior
       ChargingBehaviorAliases.once,
       Set(ChargingBehaviorNameInput, CurrentValueInput)) {
 
+  protected def computeSelectorPath(
+      chargingData: mutable.Map[String, Any],
+      currentResourceEvent: ResourceEventModel,
+      referenceTimeslot: Timeslot,
+      previousValue: Double,
+      totalCredits: Double,
+      oldAccumulatingAmount: Double,
+      newAccumulatingAmount: Double
+  ): List[String] = {
+    List(FullPriceTable.DefaultSelectorKey)
+  }
   /**
    * 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.
@@ -37,24 +37,34 @@ package gr.grnet.aquarium.charging
 
 import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
 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}
 
 /**
- * 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.
+ * The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-final class OnOffChargingBehavior
+final class VMChargingBehavior
     extends ChargingBehavior(
-      ChargingBehaviorAliases.onoff,
-      Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput)) {
-
+      ChargingBehaviorAliases.vmtime,
+      Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput),
+      List(List(Power.powerOn, Power.powerOff))) {
+
+  protected def computeSelectorPath(
+     chargingData: mutable.Map[String, Any],
+     currentResourceEvent: ResourceEventModel,
+     referenceTimeslot: Timeslot,
+     previousValue: Double,
+     totalCredits: Double,
+     oldAccumulatingAmount: Double,
+     newAccumulatingAmount: Double
+ ): List[String] = {
+    // FIXME
+    List(Power.powerOn) // compute prices for power-on state
+  }
   /**
    *
    * @param oldAmount is ignored
@@ -69,28 +79,9 @@ final class OnOffChargingBehavior
     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)
+    VMChargingBehaviorValues.isOFFValue(event.value)
   }
 
   /**
@@ -110,16 +101,16 @@ final class OnOffChargingBehavior
   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)
+    VMChargingBehaviorValues.isONValue(resourceEvent.value)
   }
 
   def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
     assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
-    assert(OnOffChargingBehaviorValues.isONValue(resourceEvent.value))
+    assert(VMChargingBehaviorValues.isONValue(resourceEvent.value))
 
     val details = resourceEvent.details
     val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
-    val newValue   = OnOffChargingBehaviorValues.OFF
+    val newValue   = VMChargingBehaviorValues.OFF
 
     resourceEvent.withDetailsAndValue(newDetails, newValue, newOccurredMillis)
   }
@@ -129,8 +120,13 @@ final class OnOffChargingBehavior
   }
 }
 
-object OnOffChargingBehavior {
-  private[this] final val TheOne = new OnOffChargingBehavior
-
-  def apply(): OnOffChargingBehavior = TheOne
+object VMChargingBehavior {
+  object Selectors {
+    object Power {
+      // When the VM is powered on
+      final val powerOn = "powerOn"
+      // When the VM is powered off
+      final val powerOff = "powerOff"
+    }
+  }
 }
@@ -40,11 +40,13 @@ package gr.grnet.aquarium.charging
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-object OnOffChargingBehaviorValues {
-  final val ON  = 1.0
-  final val OFF = 0.0
+object VMChargingBehaviorValues {
+  final val DESTROY = 2.0
+  final val ON      = 1.0
+  final val OFF     = 0.0
 
-  def isONValue (value: Double) = value == ON
-  def isOFFValue(value: Double) = value == OFF
+  def isDESTROYValue(value: Double) = value == DESTROY
+  def isONValue     (value: Double) = value == ON
+  def isOFFValue    (value: Double) = value == OFF
 }
 
index 15cce1b..c0da20d 100644 (file)
@@ -45,7 +45,7 @@ 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.Chargeslot
+import gr.grnet.aquarium.charging.{ChargingBehavior, Chargeslot}
 
 /**
  * Methods for converting accounting events to wallet entries.
@@ -91,13 +91,14 @@ object TimeslotComputations extends Loggable {
       policy: PolicyModel,
       agreement: UserAgreementModel,
       resourceType: ResourceType,
+      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
       clogOpt: Option[ContextualLogger] = None
   ): 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)
+    val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType, effectivePriceTableSelector)
     ret map {case (t,p) => (t,p.unitPrice)}
   }
 
@@ -106,6 +107,7 @@ object TimeslotComputations extends Loggable {
       resourceType: ResourceType,
       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
+      effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
       clogOpt: Option[ContextualLogger] = None
   ): List[Chargeslot] = {
 
@@ -138,10 +140,14 @@ object TimeslotComputations extends Loggable {
       //val userAgreement = agreementByTimeslot.valuesIterator.next()//getAgreementWithin(alignedTimeslot)
       val userAgreement = getAgreementWithin(alignedTimeslot)
 
-      // TODO: Factor this out, just like we did with:
-      // TODO:  val alignedTimeslots = splitTimeslotByPoliciesAndAgreements
-      // Note that most of the code is already taken from calcChangeChunks()
-      val unitPriceByTimeslot = resolveEffectiveUnitPrices(alignedTimeslot, policy, userAgreement, resourceType, Some(clog))
+      val unitPriceByTimeslot = resolveEffectiveUnitPrices(
+        alignedTimeslot,
+        policy,
+        userAgreement,
+        resourceType,
+        effectivePriceTableSelector,
+        Some(clog)
+      )
 
       // Now, the timeslots must be the same
       val finegrainedTimeslots = unitPriceByTimeslot.keySet
@@ -211,35 +217,16 @@ object TimeslotComputations extends Loggable {
      * Resolves the effective price list for each chunk of the
      * provided timeslot and returns it as a Map
      */
-    private def resolveEffectiveUnitPricesForTimeslot(
-                                               alignedTimeslot: Timeslot,
-                                               policy: PolicyModel,
-                                               agreement: UserAgreementModel,
-                                               resourceType: ResourceType
-                                               ): PriceMap = {
-
-      val role = agreement.role
-      val fullPriceTable = agreement.fullPriceTableRef match {
-        case PolicyDefinedFullPriceTableRef ⇒
-          policy.roleMapping.get(role) match {
-            case Some(fullPriceTable) ⇒
-              fullPriceTable
-
-            case None ⇒
-              throw new AquariumInternalError("Unknown role %s".format(role))
-          }
-
-        case AdHocFullPriceTableRef(fullPriceTable) ⇒
-          fullPriceTable
-      }
-
-      val effectivePriceTable = fullPriceTable.perResource.get(resourceType.name) match {
-        case None ⇒
-          throw new AquariumInternalError("Unknown resource type %s".format(role))
-
-        case Some(effectivePriceTable) ⇒
-          effectivePriceTable
-      }
+    private[this] def resolveEffectiveUnitPricesForTimeslot(
+        alignedTimeslot: Timeslot,
+        policy: PolicyModel,
+        agreement: UserAgreementModel,
+        resourceType: ResourceType,
+        effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
+    ): PriceMap = {
+
+      val fullPriceTable = agreement.computeFullPriceTable(policy)
+      val effectivePriceTable = effectivePriceTableSelector(fullPriceTable)
 
       resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
       //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
index 71e3479..e4204f6 100644 (file)
@@ -41,6 +41,8 @@ import ext.JodaTimeSerializers
 
 import gr.grnet.aquarium.util.{makeString, UTF_8_Charset}
 import java.nio.charset.Charset
+import gr.grnet.aquarium.policy.{ResourceType, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, StdPolicy}
+import gr.grnet.aquarium.charging.state.WorkingUserState
 
 /**
  * Provides conversion methods from and to JSON.
@@ -54,9 +56,38 @@ object JsonConversions {
   /**
    * The application-wide JSON formats used from the underlying lift-json library.
    */
-  implicit final val Formats = (DefaultFormats ++ JodaTimeSerializers.all)
+//  implicit final val Formats = (DefaultFormats ++ JodaTimeSerializers.all)
+  final val StdPolicyFormats = Serialization.formats(FullTypeHints(List(
+    // gather here all the "difficult" classes
+
+    // [[PolicyModel]]
+    classOf[StdPolicy],
+    classOf[ResourceType], // It is OK to leave this out
+    classOf[FullPriceTable],
+    classOf[EffectivePriceTable],
+    classOf[EffectiveUnitPrice],
+
+    // [[WorkingUserState]]
+    classOf[WorkingUserState]
+  )))
+  final val JodaFormats = JodaTimeSerializers.all
 //  implicit final val Formats = (DefaultFormats.withHints(FullTypeHints(List(classOf[AnyRef]))) ++ JodaTimeSerializers.all)
+  implicit final val Formats: Formats = StdPolicyFormats ++ JodaFormats
 //  Serialization.formats(FullTypeHints(List(classOf[AnyRef])))
+//  final val PolicyModelSerializer: Serializer[PolicyModel] = new Serializer[PolicyModel] {
+//    def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, _root_.net.liftweb.json.JValue), PolicyModel] = {
+//      case (tpe @ TypeInfo(_, _), jValue) ⇒
+//        println("!! tpe = " + tpe)
+//        println("!! jValue = " + jValue)
+//        Extraction.extract(jValue, tpe)(FullFormats).asInstanceOf[PolicyModel]
+//    }
+//
+//    def serialize(implicit format: Formats): PartialFunction[Any, _root_.net.liftweb.json.JValue] = {
+//      case x: PolicyModel ⇒
+//        println("GOT a policymodel")
+//        Extraction.decompose(x)(FullFormats)
+//    }
+//  }
 
   /**
    * Converts a value to JSON AST (Abstract Syntax Tree) by acting a bit intelligently, depending on the actual type
index 4febdcc..009d240 100644 (file)
@@ -41,7 +41,8 @@ import gr.grnet.aquarium.util.json.JsonSupport
 import com.mongodb.util.JSON
 import com.mongodb.DBObject
 import xml.NodeSeq
-import net.liftweb.json.Xml
+import net.liftweb.json.{Printer, JsonAST, Extraction, Xml}
+import gr.grnet.aquarium.policy.PolicyModel
 
 
 /**
@@ -54,6 +55,15 @@ object StdConverters {
   private[this] final lazy val builder: ConvertersBuilder = {
     val builder: ConvertersBuilder = new StdConvertersBuilder().registerDefaultConversions()
 
+    // PolicyModel ⇒ JValue
+//    builder.registerConverter(PolicyModelToJValueConverter)
+
+    // PolicyModel ⇒ PrettyJsonTextFormat
+//    builder.registerConverter(PolicyModelToPrettyJsonTextFormatConverter)
+
+    // PolicyModel ⇒ CompactJsonTextFormat
+//    builder.registerConverter(PolicyModelToCompactJsonTextFormatConverter)
+
     // Any ⇒ JValue
     builder.registerConverter(AnyToJValueConverter)
 
@@ -63,6 +73,9 @@ object StdConverters {
     // Any ⇒ CompactJsonTextFormat
     builder.registerConverter(AnyToCompactJsonTextConverter)
 
+    // JsonTextFormat ⇒ PolicyModel
+//    builder.registerConverter(JsonTextToPolicyModelConverter)
+
     // JsonTextFormat ⇒ AnyRef
     builder.registerConverter(JsonTextToObjectConverter)
 
@@ -81,6 +94,31 @@ object StdConverters {
     builder
   }
 
+//  object  PolicyModelToJValueConverter
+//  extends NonStrictSourceConverterSkeleton[PolicyModel, JValue] {
+//    protected def convertEx_(sourceValue: PolicyModel): JValue = {
+//      Extraction.decompose(sourceValue)(JsonConversions.FullFormats)
+//    }
+//  }
+
+//  object  PolicyModelToPrettyJsonTextFormatConverter
+//  extends NonStrictSourceConverterSkeleton[PolicyModel, PrettyJsonTextFormat] {
+//    protected def convertEx_(sourceValue: PolicyModel): PrettyJsonTextFormat = {
+//      val jValue = Extraction.decompose(sourceValue)(JsonConversions.FullFormats)
+//      val jDoc = JsonAST.render(jValue)
+//      PrettyJsonTextFormat(Printer.pretty(jDoc))
+//    }
+//  }
+
+//  object  PolicyModelToCompactJsonTextFormatConverter
+//  extends NonStrictSourceConverterSkeleton[PolicyModel, CompactJsonTextFormat] {
+//    protected def convertEx_(sourceValue: PolicyModel): CompactJsonTextFormat = {
+//      val jValue = Extraction.decompose(sourceValue)(JsonConversions.FullFormats)
+//      val jDoc = JsonAST.render(jValue)
+//      CompactJsonTextFormat(Printer.compact(jDoc))
+//    }
+//  }
+
   object ByteArrayToJsonTextConverter extends StrictSourceConverterSkeleton[Array[Byte], JsonTextFormat] {
     @throws(classOf[ConverterException])
     final protected def convertEx_(sourceValue: Array[Byte]) = {
@@ -123,6 +161,20 @@ object StdConverters {
     }
   }
 
+//  object JsonTextToPolicyModelConverter extends Converter {
+//   def isStrictSource = false
+//
+//   def canConvertType[S: Type, T: Type] = {
+//     erasureOf[JsonTextFormat].isAssignableFrom(erasureOf[S])
+//   }
+//
+//   @throws(classOf[ConverterException])
+//   def convertEx[T: Type](sourceValue: Any) = {
+//     // Generic deserializer from json string to a business logic model
+//     JsonConversions.jsonToObject[T](sourceValue.asInstanceOf[JsonTextFormat].value)(manifest[T], JsonConversions.FullFormats)
+//   }
+// }
+
   object JsonSupportToDBObjectConverter extends NonStrictSourceConverterSkeleton[JsonSupport, DBObject] {
     @throws(classOf[ConverterException])
     final protected def convertEx_(sourceValue: JsonSupport) = {
index 9617ca1..78797d6 100644 (file)
@@ -39,6 +39,8 @@ package resource
 import java.util.Date
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.event.model.ExternalEventModel
+import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
+import gr.grnet.aquarium.policy.StdPolicy
 
 /**
  * The model of any resource event.
index b0f7ac7..09ed51a 100644 (file)
@@ -44,7 +44,7 @@ import collection.mutable
  * @author Prodromos Gerakios <pgerakios@grnet.gr>
  */
 
-case class EffectiveUnitPrice(unitPrice: Double, when: Option[(CronSpec,CronSpec)]) { // TODO: use DSLTimeframe
+case class EffectiveUnitPrice(unitPrice: Double, when: Option[(CronSpec,CronSpec)] = None) {
 
   /* Split a timeslot T into two *sets* S and S2 consisting of timeslots such that
    *  (a) each element in S1,S2 is contained in T
index e06d779..f13bf97 100644 (file)
 
 package gr.grnet.aquarium.policy
 
+import gr.grnet.aquarium.AquariumInternalError
+import scala.annotation.tailrec
+import gr.grnet.aquarium.util.shortNameOfType
+
 /**
  * A full price table provides detailed pricing information for all resources.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-case class FullPriceTable(perResource: Map[String/*Resource*/, EffectivePriceTable]) {
-  def effectivePriceTableForResourceType(resourceType: String): Option[EffectivePriceTable] = {
-    perResource.get(resourceType)
-  }
+case class FullPriceTable(
+    perResource: Map[String/*Resource*/,
+                     Map[String/*Per-ChargingBehavior Key, "default" is the default*/, Any]]
+) {
+
+  def effectivePriceTableOfSelectorForResource(
+      selectorPath: List[String],
+      resource: String
+  ): EffectivePriceTable = {
+
+    @tailrec
+    def find(
+        selectorPath: List[String],
+        selectorData: Any
+    ): EffectivePriceTable = {
+
+      selectorPath match {
+        case Nil ⇒
+          // End of selector path. This means that the data must be an EffectivePriceTable
+          selectorData match {
+            case ept: EffectivePriceTable ⇒
+              ept
 
-  def effectivePriceTableForResourceType(resourceType: ResourceType): Option[EffectivePriceTable] = {
-    perResource.get(resourceType.name)
+            case _ ⇒
+              // TODO more informative error message (include selector path, resource?)
+              throw new AquariumInternalError("Got %s instead of an %s", selectorData, shortNameOfType[EffectivePriceTable])
+          }
+
+        case key :: tailSelectorPath ⇒
+          // Intermediate path. This means we have another round of Map[String, Any]
+          selectorData match {
+            case selectorMap: Map[_, _] ⇒
+              selectorMap.asInstanceOf[Map[String, _]].get(key) match {
+                case None ⇒
+                  throw new AquariumInternalError("Did not find value for selector %s", key)
+
+                case Some(nextSelectorData) ⇒
+                  find(tailSelectorPath, nextSelectorData)
+              }
+          }
+      }
+    }
+
+    val selectorDataOpt = perResource.get(resource)
+    if(selectorDataOpt.isEmpty) {
+      throw new AquariumInternalError("Unknown resource type '%s'", resource)
+    }
+    val selectorData = selectorDataOpt.get
+
+    find(selectorPath, selectorData)
   }
 }
+
+object FullPriceTable {
+  final val DefaultSelectorKey = "default"
+}
\ No newline at end of file
index 27023e7..2246a63 100644 (file)
@@ -38,6 +38,7 @@ package gr.grnet.aquarium.policy
 import gr.grnet.aquarium.{AquariumInternalError, Timespan}
 import gr.grnet.aquarium.util.json.JsonSupport
 import gr.grnet.aquarium.charging.ChargingBehavior
+import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
 
 /**
  * A policy is the fundamental business-related configuration of Aquarium.
@@ -114,4 +115,8 @@ object PolicyModel {
   }
 
   final object Names extends NamesT
+
+  def fromJsonString(json: String): PolicyModel = {
+    StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json))
+  }
 }
index 9f324b4..d540ff5 100644 (file)
@@ -35,7 +35,8 @@
 
 package gr.grnet.aquarium.policy
 
-import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.{AquariumInternalError, Timespan}
+import gr.grnet.aquarium.charging.ChargingBehavior
 
 /**
  *
@@ -74,4 +75,20 @@ trait UserAgreementModel extends Ordered[UserAgreementModel] {
       1
     }
   }
+
+  def computeFullPriceTable(policy: PolicyModel): FullPriceTable = {
+    this.fullPriceTableRef match {
+      case PolicyDefinedFullPriceTableRef ⇒
+        policy.roleMapping.get(role) match {
+          case Some(fullPriceTable) ⇒
+            fullPriceTable
+
+          case None ⇒
+            throw new AquariumInternalError("Unknown role '%s' in policy".format(role))
+        }
+
+      case AdHocFullPriceTableRef(fullPriceTable) ⇒
+        fullPriceTable
+    }
+  }
 }
diff --git a/src/main/scala/gr/grnet/aquarium/simulation/StdBandwidthResourceSim.scala b/src/main/scala/gr/grnet/aquarium/simulation/StdBandwidthResourceSim.scala
deleted file mode 100644 (file)
index 117af66..0000000
+++ /dev/null
@@ -1,73 +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.simulation
-import gr.grnet.aquarium.policy.PolicyModel
-import gr.grnet.aquarium.charging.{DiscreteChargingBehavior, ChargingBehavior}
-
-
-/**
- * A simulator for the standard `bandwidth` resource.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-
-class StdBandwidthResourceSim(
-    name: String = StdVMTimeResourceSim.DSLNames.name,
-    unit: String = StdVMTimeResourceSim.DSLNames.unit,
-    chargingBehavior: String = classOf[DiscreteChargingBehavior].getName
-) extends ResourceSim(name, unit, chargingBehavior) {
-
-override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
-    StdBandwidthInstanceSim(this, instanceId, owner, client)
-}
-
-
-object StdBandwidthResourceSim {
-  object DSLNames {
-    final val name = "bandwidth"
-    final val unit = "MB/Hr"
-  }
-
-  def fromPolicy(policy: PolicyModel): StdBandwidthResourceSim = {
-    val resourceType = policy.resourceTypesMap(DSLNames.name)
-    new StdBandwidthResourceSim(
-      resourceType.name,
-      resourceType.unit,
-      resourceType.chargingBehavior
-    )
-  }
-}
-
index 81086df..e99d24f 100644 (file)
@@ -38,7 +38,7 @@ package gr.grnet.aquarium.simulation
 import java.util.Date
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.charging.OnOffChargingBehaviorValues
+import gr.grnet.aquarium.charging.VMChargingBehaviorValues
 
 /**
  * A simulator for an instance of the standard `vmtime` resource.
@@ -62,7 +62,7 @@ case class StdVMTimeInstanceSim(
     newResourceEvent(
       occurredDate.getTime,
       occurredDate.getTime,
-      OnOffChargingBehaviorValues.ON,
+      VMChargingBehaviorValues.ON,
       details,
       eventVersion
     )
@@ -77,7 +77,7 @@ case class StdVMTimeInstanceSim(
     newResourceEvent(
       occurredDate.getTime,
       occurredDate.getTime,
-      OnOffChargingBehaviorValues.OFF,
+      VMChargingBehaviorValues.OFF,
       details,
       eventVersion
     )
@@ -105,7 +105,7 @@ case class StdVMTimeInstanceSim(
     newResourceEvent(
       occurredTime,
       receivedTime,
-      OnOffChargingBehaviorValues.OFF,
+      VMChargingBehaviorValues.OFF,
       details,
       eventVersion
     )
index a65537b..448efdb 100644 (file)
@@ -36,7 +36,7 @@
 package gr.grnet.aquarium.simulation
 
 import gr.grnet.aquarium.policy.PolicyModel
-import gr.grnet.aquarium.charging.{OnOffChargingBehavior, ChargingBehavior}
+import gr.grnet.aquarium.charging.{VMChargingBehavior, ChargingBehavior}
 
 
 /**
@@ -48,7 +48,7 @@ import gr.grnet.aquarium.charging.{OnOffChargingBehavior, ChargingBehavior}
 class StdVMTimeResourceSim(
     name: String = StdVMTimeResourceSim.DSLNames.name,
     unit: String = StdVMTimeResourceSim.DSLNames.unit,
-    chargingBehavior: String = classOf[OnOffChargingBehavior].getName
+    chargingBehavior: String = classOf[VMChargingBehavior].getName
 ) extends ResourceSim(name, unit, chargingBehavior) {
 
   override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
index 354e4e8..f4ae7de 100644 (file)
@@ -77,9 +77,11 @@ package object util {
   }
 
   @inline
-  def StartStopErrorHandler[S](logger: Logger,
-                               message: String,
-                               onException: ⇒ Unit = {}): PartialFunction[Throwable, Unit] = {
+  def StartStopErrorHandler[S](
+      logger: Logger,
+      message: String,
+      onException: ⇒ Unit = {}
+  ): PartialFunction[Throwable, Unit] = {
 
     case e: Throwable ⇒
       safeUnit(onException)
@@ -135,6 +137,8 @@ package object util {
     afterLastIndexOf(".", manifest[C].toString)
   }
 
+  def nameOfClass[C: ClassManifest] = classManifest[C].erasure.getName
+
   /**
    * Compute the class name excluding any leading packages and any `$` prefixes.
    *
@@ -180,34 +184,6 @@ package object util {
     "[%s] %s".format(obj.getClass, obj)
   }
 
-  /**
-   * This basically turns an [[scala.Option]] into a [[com.ckkloverdos.maybe.Maybe]] when asking a
-   * [[scala.collection.Map]] for a key.
-   *
-   * @param map The input map.
-   * @param key The key we are interested in.
-   * @tparam A The type of keys.
-   * @tparam B The type of values.
-   *
-   * @return A [[com.ckkloverdos.maybe.Just]] if a value was found, a
-   *           [[com.ckkloverdos.maybe.NoVal]] if nothing was found and a
-   *           [[com.ckkloverdos.maybe.Failed]] if some error happened.
-   */
-  @inline
-  def findFromMapAsMaybe[A, B <: AnyRef](map: scala.collection.Map[A, B], key: A): Maybe[B] = Maybe {
-    map.get(key) match {
-      case Some(value) ⇒
-        value
-      case None ⇒
-        null.asInstanceOf[B]
-    }
-  }
-
-  @inline
-  def findAndRemoveFromMap[A, B <: AnyRef](map: scala.collection.mutable.Map[A, B], key: A): Option[B] = {
-    map.remove(key)
-  }
-
   // Dear scalac. Optimize this.
   def nspaces(n: Int): String = {
     ("" /: (1 to n)) ((string, _) => string + " ")
index 5652f5e..1ec66a2 100644 (file)
@@ -8,7 +8,7 @@
 
   <logger name="ch.qos.logback" level="DEBUG"/>
 
-  <logger name="com.ckkloverdos" level="DEBUG"/>
+  <logger name="com.ckkloverdos" level="INFO"/>
 
   <logger name="gr.grnet" level="DEBUG"/>
 
index e6bb9d7..3d28c5c 100644 (file)
@@ -1,61 +1,69 @@
-{
-  "id":"3F8A9777-8C12-4529-B8E4-256AD840BEF2",
+{ "jsonClass":"gr.grnet.aquarium.policy.StdPolicy",
+
+  "id":"test-default-policy",
 
   "validityTimespan":{
     "fromMillis":0,
     "toMillis":9223372036854775807
   },
 
-  "resourceTypes":[
-    {
-      "name":"bandwidth",
-      "unit":"MB/Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.DiscreteChargingBehavior"
-    },
-    {
-      "name":"vmtime",
-      "unit":"Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.OnOffChargingBehavior"
-    },
-    {
-      "name":"diskspace",
-      "unit":"MB/Hr",
-      "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
-    }
-  ],
+  "resourceTypes":[{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"diskspace",
+    "unit":"MB/Hr",
+    "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
+  },{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"vmtime",
+    "unit":"Hr",
+    "chargingBehavior":"gr.grnet.aquarium.charging.VMChargingBehavior"
+  },{
+    "jsonClass":"gr.grnet.aquarium.policy.ResourceType",
+    "name":"addcredits",
+    "unit":"credits",
+    "chargingBehavior":"gr.grnet.aquarium.charging.OnceChargingBehavior"
+    }],
 
   "chargingBehaviors":[
-    "gr.grnet.aquarium.charging.DiscreteChargingBehavior",
-    "gr.grnet.aquarium.charging.OnOffChargingBehavior",
+    "gr.grnet.aquarium.charging.VMChargingBehavior",
     "gr.grnet.aquarium.charging.ContinuousChargingBehavior",
     "gr.grnet.aquarium.charging.OnceChargingBehavior"
   ],
-  
+
   "roleMapping":{
     "default":{
+      "jsonClass":"gr.grnet.aquarium.policy.FullPriceTable",
+
       "perResource":{
-        "bandwidth":{
-          "priceOverrides":[
-            {
+        "diskspace":{
+          "default":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
               "unitPrice":0.01
-            }
-          ]
+            }]
+          }
         },
+
         "vmtime":{
-          "priceOverrides":[
-            {
+          "powerOn":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
               "unitPrice":0.01
-            }
-          ]
+            }]
+          },
+          "powerOff":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
+              "unitPrice":0.0010
+            }]
+          }
         },
-        "diskspace":{
-          "priceOverrides":[
-            {
-              "unitPrice":0.01
-            }
-          ]
+
+        "addcredits":{
+          "default":{ "jsonClass":"gr.grnet.aquarium.policy.EffectivePriceTable",
+            "priceOverrides":[{ "jsonClass":"gr.grnet.aquarium.policy.EffectiveUnitPrice",
+              "unitPrice":-1.0
+            }]
+          }
         }
       }
     }
   }
-}
\ No newline at end of file
+}
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.simulation
+package gr.grnet.aquarium.event.model
 
-import java.util.Date
-import gr.grnet.aquarium.store.RecordID
+import org.junit.Test
+import gr.grnet.aquarium.event.model.resource.StdResourceEvent
 
 /**
- * A simulator for an instance of the standard `bandwidth` resource.
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 
-case class StdBandwidthInstanceSim(override val resource: StdBandwidthResourceSim,
-                                   override val instanceId: String,
-                                   override val owner: UserSim,
-                                   override val client: ClientSim)
-extends ResourceInstanceSim(resource, instanceId, owner, client) {
-  def useBandwidth(occurredDate: Date,
-                   megaBytes: Double,
-                   details: Map[String, String] = Map(),
-                   eventVersion: String = "1.0") = {
+class StdResourceEventTest {
+  @Test
+  def testJson() {
+    val rc = StdResourceEvent("id-2", 1000L, 1000L, "luv@g.com", "pithos", "disk", "/disk1", 1.0, "1.0", Map())
+    val json = rc.toJsonString
+    val obj  = StdResourceEvent.fromJsonString(json)
 
-    newResourceEvent(
-     occurredDate.getTime,
-     occurredDate.getTime,
-     megaBytes,
-     details,
-     eventVersion
-    )
+    assert(rc == obj)
   }
-}
\ No newline at end of file
+}
index f320cbf..3dc1160 100644 (file)
@@ -37,8 +37,10 @@ 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}
+import gr.grnet.aquarium.charging.{OnceChargingBehavior, ContinuousChargingBehavior, VMChargingBehavior}
+import gr.grnet.aquarium.charging.VMChargingBehavior.Selectors.Power
+import gr.grnet.aquarium.util.nameOfClass
+import FullPriceTable.DefaultSelectorKey
 
 /**
  *
@@ -47,38 +49,41 @@ import gr.grnet.aquarium.converter.{StdConverters, PrettyJsonTextFormat}
 
 class StdPolicyTest {
   final lazy val policy = StdPolicy(
-    id = "policy-1",
+    id = "default-policy",
     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)
+      ResourceType("diskspace", "MB/Hr", nameOfClass[ContinuousChargingBehavior]),
+      ResourceType("vmtime",    "Hr",    nameOfClass[VMChargingBehavior])
     ),
 
     chargingBehaviors = Set(
-      classOf[DiscreteChargingBehavior].getName,
-      classOf[OnOffChargingBehavior].getName,
-      classOf[ContinuousChargingBehavior].getName,
-      classOf[OnceChargingBehavior].getName
+      nameOfClass[VMChargingBehavior],
+      nameOfClass[ContinuousChargingBehavior],
+      nameOfClass[OnceChargingBehavior]
     ),
 
     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)
+        "diskspace" -> Map(
+          DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)
+        ),
+        "vmtime" -> Map(
+          Power.powerOn  -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil),
+          Power.powerOff -> EffectivePriceTable(EffectiveUnitPrice(0.001) :: Nil) // cheaper when the VM is OFF
+        )
       ))
     )
   )
 
   @Test
   def testJson(): Unit = {
-    val converters = StdConverters.AllConverters
-    val json = converters.convertEx[PrettyJsonTextFormat](policy)
-    val obj = converters.convertEx[StdPolicy](json)
+    val json = policy.toJsonString
+    val obj = PolicyModel.fromJsonString(json)
+
+    println(json)
 
     assert(policy == obj)
   }
index 2834534..da4c635 100644 (file)
@@ -45,11 +45,20 @@ import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumEx
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.computation.BillingMonthInfo
 import gr.grnet.aquarium.charging._
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy, PolicyModel}
-import gr.grnet.aquarium.Timespan
-import gr.grnet.aquarium.charging.state.UserStateBootstrap
+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
 
 
 /**
@@ -72,21 +81,18 @@ class UserStateComputationsTest extends Loggable {
     parentID = None,
     validityTimespan = Timespan(0),
     resourceTypes = Set(
-      ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
-      ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+      ResourceType("vmtime", "Hr", classOf[VMChargingBehavior].getName),
       ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
     ),
     chargingBehaviors = Set(
-      classOf[DiscreteChargingBehavior].getName,
-      classOf[OnOffChargingBehavior].getName,
+      classOf[VMChargingBehavior].getName,
       classOf[ContinuousChargingBehavior].getName,
       classOf[OnceChargingBehavior].getName
     ),
     roleMapping = Map(
       "default" -> FullPriceTable(Map(
-        "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(BandwidthUnitPrice, None) :: Nil),
-        "vmtime"    -> EffectivePriceTable(EffectiveUnitPrice(VMTimeUnitPrice, None) :: Nil),
-        "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(DiskspaceUnitPrice, None) :: Nil)
+        "diskspace" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)),
+        "vmtime"    -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil))
       ))
     )
   )
@@ -137,7 +143,7 @@ class UserStateComputationsTest extends Loggable {
 
           creditsForDiscrete(currentValue)
 
-        case ChargingBehaviorAliases.onoff ⇒
+        case ChargingBehaviorAliases.vmtime ⇒
           val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
           val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
 
@@ -158,7 +164,7 @@ class UserStateComputationsTest extends Loggable {
       Map(
         ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
         ChargingBehaviorAliases.discrete   -> "currentValue * %s".format(DiscreteUnitPrice),
-        ChargingBehaviorAliases.onoff      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
+        ChargingBehaviorAliases.vmtime      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
         ChargingBehaviorAliases.once       -> "currentValue"))
   }
 
@@ -175,7 +181,6 @@ class UserStateComputationsTest extends Loggable {
   // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
   val VMTimeResourceSim    = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
   val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
-  val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
 
   // There are two client services, synnefo and pithos.
   val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
@@ -200,7 +205,7 @@ class UserStateComputationsTest extends Loggable {
 
   aquarium.policyStore.insertPolicy(DefaultPolicy)
 
-  val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore)
+  val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim), aquarium.resourceEventStore)
   val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
 
   val UserCKKL  = AquariumSim_.newUser("CKKL", UserCreationDate)
@@ -224,7 +229,6 @@ class UserStateComputationsTest extends Loggable {
   // - synnefo is for VMTime and Bandwidth
   // - pithos is for Diskspace
   val VMTimeInstanceSim    = VMTimeResourceSim.newInstance   ("VM.1",   UserCKKL, Synnefo)
-  val BandwidthInstanceSim = BandwidthResourceSim.newInstance("3G.1",   UserCKKL, Synnefo)
   val DiskInstanceSim      = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
 
   private[this]
@@ -408,10 +412,6 @@ class UserStateComputationsTest extends Loggable {
     DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
     DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
 
-    // 100MB 3G bandwidth
-    val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1)
-    BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0)
-
     // ... and one "future" event
     DiskInstanceSim.consumeMB(
       StartOfBillingYearDateCalc.copy.