Rename Configurator to Aquarium
[aquarium] / src / test / scala / gr / grnet / aquarium / user / UserStateComputationsTest.scala
index 6191398..c602707 100644 (file)
@@ -1,16 +1,55 @@
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
 package gr.grnet.aquarium.user
 
-import gr.grnet.aquarium.Configurator
 import gr.grnet.aquarium.store.memory.MemStore
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.logic.accounting.dsl._
 import gr.grnet.aquarium.logic.accounting.{Policy, Accounting}
-import gr.grnet.aquarium.util.{Loggable, ContextualLogger, justForSure}
+import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
 import gr.grnet.aquarium.simulation._
-import gr.grnet.aquarium.simulation.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
-import com.ckkloverdos.maybe.{Maybe, Just, NoVal}
-import gr.grnet.aquarium.logic.accounting.algorithm.SimpleCostPolicyAlgorithmCompiler
+import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
+import com.ckkloverdos.maybe.{Maybe, Just}
 import org.junit.{Assert, Ignore, Test}
+import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler}
+import gr.grnet.aquarium.{AquariumException}
+import gr.grnet.aquarium.Aquarium.{Instance ⇒ AquariumInstance}
+import gr.grnet.aquarium.computation.{UserState, BillingMonthInfo, UserStateComputations}
+import gr.grnet.aquarium.computation.reason.MonthlyBillingCalculation
+import org.apache.ivy.util.Configurator
 
 
 /**
@@ -18,7 +57,17 @@ import org.junit.{Assert, Ignore, Test}
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 class UserStateComputationsTest extends Loggable {
-  val PolicyYAML = """
+  final val DoubleDelta = 0.001
+
+  final val BandwidthPriceUnit = 3.3 //
+  final val VMTimePriceUnit    = 1.5 //
+  final val DiskspacePriceUnit = 2.7 //
+
+  final val OnOffPriceUnit = VMTimePriceUnit
+  final val ContinuousPriceUnit = DiskspacePriceUnit
+  final val DiscretePriceUnit = BandwidthPriceUnit
+
+  final val PolicyYAML = """
 aquariumpolicy:
   resources:
     - resource:
@@ -54,9 +103,9 @@ aquariumpolicy:
   pricelists:
     - pricelist:
       name: default
-      bandwidth: 1.0
-      vmtime: 1.0
-      diskspace: 1.0
+      bandwidth: %s
+      vmtime: %s
+      diskspace: %s
       effective:
         from: 0
 
@@ -74,14 +123,86 @@ aquariumpolicy:
       algorithm: default
       pricelist: default
       creditplan: default
-  """
+  """.format(
+    BandwidthPriceUnit,
+    VMTimePriceUnit,
+    DiskspacePriceUnit
+  )
 
   val Computations = new UserStateComputations
 
   val DefaultPolicy = new DSL{} parse PolicyYAML
   val DefaultAccounting = new Accounting{}
-  val DefaultCompiler  = SimpleCostPolicyAlgorithmCompiler
-  val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
+  
+  val DefaultAlgorithm = new ExecutableCostPolicyAlgorithm {
+    def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
+      hrs(timeDelta) * oldTotalAmount * ContinuousPriceUnit
+
+    final val creditsForDiskspace = creditsForContinuous(_, _)
+    
+    def creditsForDiscrete(currentValue: Double) =
+      currentValue * DiscretePriceUnit
+
+    final val creditsForBandwidth = creditsForDiscrete(_)
+
+    def creditsForOnOff(timeDelta: Double) =
+      hrs(timeDelta) * OnOffPriceUnit
+
+    final val creditsForVMTime = creditsForOnOff(_)
+
+    @inline private[this]
+    def hrs(millis: Double) = millis / 1000 / 60 / 60
+
+    def apply(vars: Map[DSLCostPolicyVar, Any]): Double = {
+      vars.apply(DSLCostPolicyNameVar) match {
+        case DSLCostPolicyNames.continuous ⇒
+          val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+          val oldTotalAmount = vars(DSLOldTotalAmountVar).asInstanceOf[Double]
+          val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
+
+          Assert.assertEquals(ContinuousPriceUnit, unitPrice, DoubleDelta)
+
+          creditsForContinuous(timeDelta, oldTotalAmount)
+
+        case DSLCostPolicyNames.discrete ⇒
+          val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+          val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
+
+          Assert.assertEquals(DiscretePriceUnit, unitPrice, DoubleDelta)
+
+          creditsForDiscrete(currentValue)
+
+        case DSLCostPolicyNames.onoff ⇒
+          val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+          val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
+
+          Assert.assertEquals(OnOffPriceUnit, unitPrice, DoubleDelta)
+
+          creditsForOnOff(timeDelta)
+
+        case DSLCostPolicyNames.once ⇒
+          val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
+          currentValue
+
+        case name ⇒
+          throw new AquariumException("Unknown cost policy %s".format(name))
+      }
+    }
+
+    override def toString = "DefaultAlgorithm(%s)".format(
+      Map(
+        DSLCostPolicyNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousPriceUnit),
+        DSLCostPolicyNames.discrete   -> "currentValue * %s".format(DiscretePriceUnit),
+        DSLCostPolicyNames.onoff      -> "hrs(timeDelta) * %s".format(OnOffPriceUnit),
+        DSLCostPolicyNames.once       -> "currentValue"))
+  }
+
+  val DefaultCompiler  = new CostPolicyAlgorithmCompiler {
+    def compile(definition: String): ExecutableCostPolicyAlgorithm = {
+      DefaultAlgorithm
+    }
+  }
+  //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
 
   val VMTimeDSLResource = DefaultPolicy.findResource("vmtime").get
 
@@ -92,13 +213,13 @@ aquariumpolicy:
   val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
 
   // There are two client services, synnefo and pithos.
-  val TheUIDGenerator: UIDGenerator = new ConcurrentVMLocalUIDGenerator
+  val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
   val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
   val Pithos  = ClientSim("pithos" )(TheUIDGenerator)
 
-  val mc = Configurator.MasterConfigurator.withStoreProviderClass(classOf[MemStore])
-  Policy.withConfigurator(mc)
-  val StoreProvider = mc.storeProvider
+  val aquarium = AquariumInstance.withStoreProviderClass(classOf[MemStore])
+  Policy.withConfigurator(aquarium)
+  val StoreProvider = aquarium.storeProvider
   val ResourceEventStore = StoreProvider.resourceEventStore
 
   val StartOfBillingYearDateCalc = new MutableDateCalc(2012,  1, 1)
@@ -123,8 +244,8 @@ aquariumpolicy:
 
   val UserCKKL  = Aquarium.newUser("CKKL", UserCreationDate)
 
-  val InitialUserState = Computations.createInitialUserState(
-    userId = UserCKKL.userId,
+  val InitialUserState = UserState.createInitialUserState(
+    userID = UserCKKL.userId,
     userCreationMillis = UserCreationDate.getTime,
     isActive = true,
     credits = 0.0,
@@ -141,22 +262,21 @@ aquariumpolicy:
 
   private[this]
   def showUserState(clog: ContextualLogger, userState: UserState) {
-    val id = userState.id
+    val id = userState._id
     val parentId = userState.parentUserStateId
     val credits = userState.creditsSnapshot.creditAmount
-    val newWalletEntries = userState.newWalletEntries
-    val changeReasonCode = userState.lastChangeReasonCode
+    val newWalletEntries = userState.newWalletEntries.map(_.toDebugString)
     val changeReason = userState.lastChangeReason
-    userState.implicitlyIssuedSnapshot
+    val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString())
+    val latestResourceEvents = userState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString())
 
-    clog.indent()
     clog.debug("_id = %s", id)
     clog.debug("parentId = %s", parentId)
     clog.debug("credits = %s", credits)
-    clog.debug("changeReasonCode = %s", changeReasonCode)
     clog.debug("changeReason = %s", changeReason)
-    clog.debugSeq("newWalletEntries", newWalletEntries.map(_.toDebugString), 0)
-    clog.unindent()
+    clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
+    clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
+    clog.debugSeq("newWalletEntries", newWalletEntries, 0)
   }
 
   private[this]
@@ -183,7 +303,7 @@ aquariumpolicy:
       DefaultAccounting,
       DefaultCompiler,
       MonthlyBillingCalculation(billingMonthInfo),
-      Just(clog)
+      Some(clog)
     )
   }
   
@@ -191,7 +311,7 @@ aquariumpolicy:
   def justUserState(userStateM: Maybe[UserState]): UserState = {
     userStateM match {
       case Just(userState) ⇒ userState
-      case _ ⇒ throw new Exception("Unexpected %s".format(userStateM))
+      case _ ⇒ throw new AquariumException("Unexpected %s".format(userStateM))
     }
   }
   
@@ -214,9 +334,11 @@ aquariumpolicy:
   /**
    * Test a sequence of ON, OFF vmtime events.
    */
-  @Test @Ignore
-  def testFullOnOff_FullMonthBilling: Unit = {
-    val clog = ContextualLogger.fromOther(NoVal, logger, "testFullOnOff()")
+  @Ignore
+  @Test
+  def testFullOnOff: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
+    clog.begin()
 
     ResourceEventStore.clearResourceEvents()
     val OnOffDurationHrs = 10
@@ -227,32 +349,24 @@ aquariumpolicy:
       OnOffDurationHrs
     )
 
-    // Make a value map for the billable OFF event
-    val valueMap = OnOffCostPolicy.makeValueMap(
-      totalCredits = 0,
-      oldTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
-      newTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
-      timeDelta = OnOffDurationMillis,
-      previousValue = OnOffCostPolicyValues.ON,
-      currentValue = OnOffCostPolicyValues.OFF,
-      unitPrice = DefaultPolicy.findPriceList("default").get.prices(VMTimeDSLResource)
-    )
-
-    val credits = justForSure(DefaultAlgorithm(valueMap)).get
+    val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
 
     showResourceEvents(clog)
 
-    val userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
-    val userState = justUserState(userStateM)
-    
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
     showUserState(clog, userState)
 
     expectCredits(clog, credits, userState)
+
+    clog.end()
   }
 
+  @Ignore
   @Test
-  def testLonelyON_FullMonthBilling: Unit = {
-    val clog = ContextualLogger.fromOther(NoVal, logger, "testLonelyON()")
+  def testLonelyON: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
+    clog.begin()
 
     ResourceEventStore.clearResourceEvents()
     
@@ -262,37 +376,56 @@ aquariumpolicy:
     val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
     val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
 
-    VMTimeInstanceSim.newON(
-      JanStartDate
-    )
+    VMTimeInstanceSim.newON(JanStartDate)
 
-    // Make a value map for the billable *implicit* OFF event
-    val valueMap = OnOffCostPolicy.makeValueMap(
-      totalCredits = 0,
-      oldTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
-      newTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
-      timeDelta = OnOffImplicitDurationMillis,
-      previousValue = OnOffCostPolicyValues.ON,
-      currentValue = OnOffCostPolicyValues.OFF,
-      unitPrice = DefaultPolicy.findPriceList("default").get.prices(VMTimeDSLResource)
-    )
+    val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
+
+    showResourceEvents(clog)
+
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
+    showUserState(clog, userState)
+
+    expectCredits(clog, credits, userState)
 
-    val credits = justForSure(DefaultAlgorithm(valueMap)).get
+    clog.end()
+  }
+
+//  @Ignore
+  @Test
+  def testOrphanOFF: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
+    clog.begin()
+
+    ResourceEventStore.clearResourceEvents()
+
+    val JanStart = new MutableDateCalc(2012, 01, 01)
+    val JanEnd = JanStart.copy.goEndOfThisMonth
+    val JanStartDate = JanStart.toDate
+    val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
+    val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
+
+    VMTimeInstanceSim.newOFF(JanStartDate)
+
+    // This is an orphan event, so no credits will be charged
+    val credits = 0
 
     showResourceEvents(clog)
 
-    val userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
-    val userState = justUserState(userStateM)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
 
     showUserState(clog, userState)
 
     expectCredits(clog, credits, userState)
+
+    clog.end()
   }
 
   @Ignore
   @Test
   def testOne: Unit = {
-    val clog = ContextualLogger.fromOther(NoVal, logger, "testOne()")
+    val clog = ContextualLogger.fromOther(None, logger, "testOne()")
+    clog.begin()
 
     // Let's create our dates of interest
     val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
@@ -330,8 +463,10 @@ aquariumpolicy:
 
     clog.debugMap("DefaultResourcesMap", DefaultResourcesMap.map, 1)
 
-    val userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
-    val userState = justUserState(userStateM)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
     showUserState(clog, userState)
+
+    clog.end()
   }
 }
\ No newline at end of file