Fix a compilation error (did not implement a MemStore method)
[aquarium] / src / test / scala / gr / grnet / aquarium / user / UserStateComputationsTest.scala
index 4ab8da3..7fe30b2 100644 (file)
@@ -1,15 +1,59 @@
+/*
+ * 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 org.junit.Test
-import gr.grnet.aquarium.Configurator
-import gr.grnet.aquarium.store.memory.MemStore
-import gr.grnet.aquarium.util.date.MutableDateCalc
+import gr.grnet.aquarium.store.memory.MemStoreProvider
 import gr.grnet.aquarium.logic.accounting.dsl._
-import java.util.Date
-import simulation.{ConcurrentVMLocalUIDGenerator, ClientServiceSim, UserSim}
-import gr.grnet.aquarium.logic.accounting.{Policy, Accounting}
 import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
-import com.ckkloverdos.maybe.{Just, NoVal}
+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.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.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
 
 
 /**
@@ -17,138 +61,362 @@ import com.ckkloverdos.maybe.{Just, NoVal}
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
 class UserStateComputationsTest extends Loggable {
-  val PolicyYAML = """
-aquariumpolicy:
-  resources:
-    - resource:
-      name: bandwidth
-      unit: MB/hr
-      complex: false
-      costpolicy: discrete
-    - resource:
-      name: vmtime
-      unit: Hour
-      complex: true
-      costpolicy: onoff
-      descriminatorfield: vmid
-    - resource:
-      name: diskspace
-      unit: MB/hr
-      complex: false
-      costpolicy: continuous
-
-  implicitvars:
-    - price
-    - volume
-
-  algorithms:
-    - algorithm:
-      name: default
-      bandwidth: $NotNow
-      vmtime: $NotNow
-      diskspace: $NotNow
-      effective:
-        from: 0
-
-  pricelists:
-    - pricelist:
-      name: default
-      bandwidth: 1.0
-      vmtime: 1.0
-      diskspace: 1.0
-      effective:
-        from: 0
-
-  creditplans:
-    - creditplan:
-      name: default
-      credits: 100
-      at: "00 00 1 * *"
-      effective:
-        from: 0
-
-  agreements:
-    - agreement:
-      name: default
-      algorithm: default
-      pricelist: default
-      creditplan: default
-  """
-
-  val DefaultPolicy = new DSL{}.parse(PolicyYAML)
-
-  // TODO: integrate this with the rest of the simulation stuff
-  // TODO: since, right now, the resource strings have to be given twice
-  val VMTimeResource    = DSLComplexResource("vmtime",    "Hr",    OnOffCostPolicy,      "")
-  val DiskspaceResource = DSLComplexResource("diskspace", "MB/Hr", ContinuousCostPolicy, "")
-  val BandwidthResource = DSLComplexResource("bandwidth", "MB/Hr", DiscreteCostPolicy,   "")
-  val DefaultResourcesMap = new DSLResourcesMap(VMTimeResource :: DiskspaceResource :: BandwidthResource :: Nil)
+  final val DoubleDelta = 0.001
+
+  final val BandwidthUnitPrice = 3.3 //
+  final val VMTimeUnitPrice    = 1.5 //
+  final val DiskspaceUnitPrice = 2.7 //
+
+  final val OnOffUnitPrice = VMTimeUnitPrice
+  final val ContinuousUnitPrice = DiskspaceUnitPrice
+  final val DiscreteUnitPrice = BandwidthUnitPrice
+
+  final val DefaultPolicy: PolicyModel = StdPolicy(
+    id = "policy-1",
+    parentID = None,
+    validityTimespan = Timespan(0),
+    resourceTypes = Set(
+      ResourceType("bandwidth", "MB/Hr", DiscreteChargingBehavior),
+      ResourceType("vmtime",    "Hr",    OnOffChargingBehavior),
+      ResourceType("diskspace", "MB/Hr", ContinuousChargingBehavior)
+    ),
+    chargingBehaviorClasses = Set(
+      DiscreteChargingBehavior.getClass.getName,
+      OnOffChargingBehavior.getClass.getName,
+      ContinuousChargingBehavior.getClass.getName
+    ),
+    roleMapping = Map(
+      "default" -> FullPriceTable(Map(
+        "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(BandwidthUnitPrice, None) :: Nil),
+        "vmtime"    -> EffectivePriceTable(EffectiveUnitPrice(VMTimeUnitPrice, None) :: Nil),
+        "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(DiskspaceUnitPrice, None) :: Nil)
+      ))
+    )
+  )
+
+  val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties).
+    update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
+    build()
+
+  val ResourceEventStore = aquarium.resourceEventStore
+
+  val Computations = aquarium.userStateComputations
+
+  val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
+    def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
+      hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
+
+    final val creditsForDiskspace = creditsForContinuous(_, _)
+    
+    def creditsForDiscrete(currentValue: Double) =
+      currentValue * DiscreteUnitPrice
+
+    final val creditsForBandwidth = creditsForDiscrete(_)
+
+    def creditsForOnOff(timeDelta: Double) =
+      hrs(timeDelta) * OnOffUnitPrice
+
+    final val creditsForVMTime = creditsForOnOff(_)
+
+    @inline private[this]
+    def hrs(millis: Double) = millis / 1000 / 60 / 60
+
+    def apply(vars: Map[ChargingInput, Any]): Double = {
+      vars.apply(ChargingBehaviorNameInput) match {
+        case ChargingBehaviorNames.continuous ⇒
+          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
+          val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
+          val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
+
+          Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
+
+          creditsForContinuous(timeDelta, oldTotalAmount)
+
+        case ChargingBehaviorNames.discrete ⇒
+          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
+          val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
+
+          Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
+
+          creditsForDiscrete(currentValue)
+
+        case ChargingBehaviorNames.onoff ⇒
+          val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
+          val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
+
+          Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
+
+          creditsForOnOff(timeDelta)
+
+        case ChargingBehaviorNames.once ⇒
+          val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
+          currentValue
+
+        case name ⇒
+          throw new AquariumException("Unknown cost policy %s".format(name))
+      }
+    }
+
+    override def toString = "DefaultAlgorithm(%s)".format(
+      Map(
+        ChargingBehaviorNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
+        ChargingBehaviorNames.discrete   -> "currentValue * %s".format(DiscreteUnitPrice),
+        ChargingBehaviorNames.onoff      -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
+        ChargingBehaviorNames.once       -> "currentValue"))
+  }
+
+  val DefaultCompiler  = new CostPolicyAlgorithmCompiler {
+    def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
+      DefaultAlgorithm
+    }
+  }
+  //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
+
+  val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
+
+  // For this to work, the definitions must match those in the YAML above.
+  // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
+  val VMTimeResourceSim    = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
+  val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
+  val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
 
   // There are two client services, synnefo and pithos.
-  val TheUIDGenerator = new ConcurrentVMLocalUIDGenerator
-  val Synnefo = ClientServiceSim("synnefo")(TheUIDGenerator)
-  val Pithos  = ClientServiceSim("pithos")(TheUIDGenerator)
+  val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
+  val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
+  val Pithos  = ClientSim("pithos" )(TheUIDGenerator)
+
+  val StartOfBillingYearDateCalc = new MutableDateCalc(2012,  1, 1)
+  val UserCreationDate           = new MutableDateCalc(2011, 11, 1).toDate
+
+  val BillingMonthInfoJan = {
+    val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
+    BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
+  }
+  val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012,  2, 1))
+  val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012,  3, 1))
+
+  // Store the default policy
+  val policyDateCalc        = StartOfBillingYearDateCalc.copy
+  val policyOccurredMillis  = policyDateCalc.toMillis
+  val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
+  val policyValidToMillis   = policyDateCalc.copy.goNextYear.toMillis
+
+  aquarium.policyStore.insertPolicy(DefaultPolicy)
+
+  val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore)
+  val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
+
+  val UserCKKL  = AquariumSim_.newUser("CKKL", UserCreationDate)
+
+//  val InitialUserState = UserState.createInitialUserState(
+//    userID = UserCKKL.userID,
+//    userCreationMillis = UserCreationDate.getTime,
+//    totalCredits = 0.0,
+//    initialRole = "default",
+//    initialAgreement = DSLAgreement.DefaultAgreementName
+//  )
+
+  val UserStateBootstrapper = UserStateBootstrap(
+    userID = UserCKKL.userID,
+    userCreationMillis = UserCreationDate.getTime(),
+    initialAgreement = StdUserAgreement("", None, Timespan(0), "default", PolicyDefinedFullPriceTableRef),
+    initialCredits = 0.0
+  )
+
+  // By convention
+  // - synnefo is for VMTime and Bandwidth
+  // - pithos is for Diskspace
+  val VMTimeInstanceSim    = VMTimeResourceSim.newInstance   ("VM.1",   UserCKKL, Synnefo)
+  val BandwidthInstanceSim = BandwidthResourceSim.newInstance("3G.1",   UserCKKL, Synnefo)
+  val DiskInstanceSim      = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
+
+  private[this]
+  def showUserState(clog: ContextualLogger, userState: UserState) {
+    val id = userState._id
+    val parentId = userState.parentUserStateIDInStore
+    val credits = userState.totalCredits
+    val newWalletEntries = userState.newWalletEntries.map(_.toDebugString)
+    val changeReason = userState.lastChangeReason
+    val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
+    val latestResourceEvents = userState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
+
+    clog.debug("_id = %s", id)
+    clog.debug("parentId = %s", parentId)
+    clog.debug("credits = %s", credits)
+    clog.debug("changeReason = %s", changeReason)
+    clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
+    clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
+    clog.debugSeq("newWalletEntries", newWalletEntries, 0)
+  }
+
+  private[this]
+  def showResourceEvents(clog: ContextualLogger): Unit = {
+    clog.debug("")
+    clog.begin("Events by OccurredMillis")
+    clog.withIndent {
+      for(event <- UserCKKL.myResourceEventsByOccurredDate) {
+        clog.debug(event.toDebugString)
+      }
+    }
+    clog.end("Events by OccurredMillis")
+    clog.debug("")
+  }
+
+  private[this]
+  def doFullMonthlyBilling(
+      clog: ContextualLogger,
+      billingMonthInfo: BillingMonthInfo,
+      billingTimeMillis: Long) = {
+
+    Computations.doMonthBillingUpTo(
+      billingMonthInfo,
+      billingTimeMillis,
+      UserStateBootstrapper,
+      DefaultResourcesMap,
+      MonthlyBillingCalculation(NoSpecificChangeReason(), billingMonthInfo),
+      aquarium.userStateStore.insertUserState,
+      Some(clog)
+    )
+  }
+
+  private[this]
+  def expectCredits(
+      clog: ContextualLogger,
+      creditsConsumed: Double,
+      userState: UserState,
+      accuracy: Double = 0.001
+  ): Unit = {
+
+    val computed = userState.totalCredits
+    Assert.assertEquals(-creditsConsumed, computed, accuracy)
+
+    clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
+  }
+
+  private[this]
+  def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
 
+  private[this]
+  def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
+
+  /**
+   * Test a sequence of ON, OFF vmtime events.
+   */
+  @Ignore
   @Test
-  def testOne: Unit = {
-    val clog = ContextualLogger.fromOther(NoVal, logger, "testOne()")
-    val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
-//    println("StartOfBillingYearDateCalc = %s".format(StartOfBillingYearDateCalc))
-    val UserCreationDateCalc = StartOfBillingYearDateCalc.copy.goMinusMonths(2)
-//    println("UserCreationDateCalc = %s".format(UserCreationDateCalc))
+  def testFullOnOff: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
+    clog.begin()
 
-    val computer = new UserStateComputations
+    ResourceEventStore.clearResourceEvents()
+    val OnOffDurationHrs = 10
+    val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
 
-    val mc = Configurator.MasterConfigurator.withStoreProviderClass(classOf[MemStore])
-    Policy.withConfigurator(mc)
+    VMTimeInstanceSim.newONOFF(
+      new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
+      OnOffDurationHrs
+    )
+
+    val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
+
+    showResourceEvents(clog)
+
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
 
-    val storeProvider = mc.storeProvider
-    val userStateStore = storeProvider.userStateStore
-    val resourceEventStore = storeProvider.resourceEventStore
-    val policyStore = storeProvider.policyStore
+    showUserState(clog, userState)
 
-    val policyOccurredMillis  = StartOfBillingYearDateCalc.toMillis
-    val policyValidFromMillis = StartOfBillingYearDateCalc.copy.goPreviousYear.toMillis
-    val policyValidToMillis   = StartOfBillingYearDateCalc.copy.goNextYear.toMillis
-    policyStore.storePolicyEntry(DefaultPolicy.toPolicyEntry(policyOccurredMillis, policyValidFromMillis, policyValidToMillis))
+    expectCredits(clog, credits, userState)
 
-    // A new user is created on 2012-01-15 00:00:00.000
-    val UserCKKL  = UserSim("CKKL", UserCreationDateCalc.toDate, storeProvider.resourceEventStore)
+    clog.end()
+  }
+
+  @Ignore
+  @Test
+  def testLonelyON: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
+    clog.begin()
 
-    // By convention
-    // - synnefo is for VMTime and Bandwidth
-    // - pithos is for Diskspace
-    val VMTimeInstance    = Synnefo.newVMTime   (UserCKKL, "VM.1")
-    val BandwidthInstance = Synnefo.newBandwidth(UserCKKL, "3G.1")
-    val DiskInstance      = Pithos .newDiskspace(UserCKKL, "DISK.1")
+    ResourceEventStore.clearResourceEvents()
+    
+    val JanStart = new MutableDateCalc(2012, 01, 01)
+    val JanEnd = JanStart.copy.goEndOfThisMonth
+    val JanStartDate = JanStart.toDate
+    val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
+    val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
+
+    VMTimeInstanceSim.newON(JanStartDate)
+
+    val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
+
+    showResourceEvents(clog)
+
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
+
+    showUserState(clog, userState)
+
+    expectCredits(clog, credits, userState)
+
+    clog.end()
+  }
+
+//  @Ignore
+  @Test
+  def testOrphanOFF: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
+    clog.begin()
+
+    ResourceEventStore.clearResourceEvents()
+
+    val JanStart = new MutableDateCalc(2012, 01, 01)
+    val JanEnd = JanStart.copy.goEndOfThisMonth
+    val JanStartDate = JanStart.toDate
+    val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
+    val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
+
+    VMTimeInstanceSim.newOFF(JanStartDate)
+
+    // This is an orphan event, so no credits will be charged
+    val credits = 0
+
+    showResourceEvents(clog)
+
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
+
+    showUserState(clog, userState)
+
+    expectCredits(clog, credits, userState)
+
+    clog.end()
+  }
+
+  @Ignore
+  @Test
+  def testOne: Unit = {
+    val clog = ContextualLogger.fromOther(None, logger, "testOne()")
+    clog.begin()
 
     // Let's create our dates of interest
-    val vmStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
-//    println("vmStartDateCalc = %s".format(vmStartDateCalc))
-    // 2012-01-16 01:00:00.000
-    val vmStartDate = vmStartDateCalc.toDate
+    val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
+    val VMStartDate = VMStartDateCalc.toDate
 
     // Within January, create one VM ON-OFF ...
-    val onOff1_M = VMTimeInstance.newONOFF(vmStartDate, 9)
+    VMTimeInstanceSim.newONOFF(VMStartDate, 9)
 
     val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
-    // 2012-01-16 04:00:00.000
     val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
-    // 2012-01-17 05:00:00.000
     val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
     val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
 
     // ... and two diskspace changes
-    val consume1_M = DiskInstance.consumeMB(diskConsumptionDate1, 99)
-    val consume2_M = DiskInstance.consumeMB(diskConsumptionDate2, 23)
+    DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
+    DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
 
     // 100MB 3G bandwidth
     val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1)
-    BandwidthInstance.useBandwidth(bwDateCalc.toDate, 100.0)
+    BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0)
 
     // ... and one "future" event
-    // 2012-02-07 07:07:07.007
-    DiskInstance.consumeMB(
+    DiskInstanceSim.consumeMB(
       StartOfBillingYearDateCalc.copy.
         goNextMonth.goPlusDays(6).
         goPlusHours(7).
@@ -157,43 +425,16 @@ aquariumpolicy:
         goPlusMillis(7).toDate,
       777)
 
-    clog.debug("")
-    clog.debug("=== Events by OccurredMillis ===")
-    clog.withIndent {
-      for(event <- UserCKKL.myResourceEventsByOccurredDate) {
-        clog.debug(event.toDebugString(DefaultResourcesMap))
-      }
-    }
-    clog.debug("=== Events by OccurredMillis ===")
-    clog.debug("")
+    showResourceEvents(clog)
 
-    val billingMonthInfo = BillingMonthInfo.fromDateCalc(StartOfBillingYearDateCalc)
+    // Policy: from 2012-01-01 to Infinity
 
-    val initialUserState = computer.createFirstUserState(
-      userId = UserCKKL.userId,
-      millis = StartOfBillingYearDateCalc.copy.goPreviousYear.toMillis
-    )
+    clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1)
 
-    val userStateM = computer.doFullMonthlyBilling(
-      UserCKKL.userId,
-      billingMonthInfo,
-      userStateStore,
-      resourceEventStore,
-      policyStore,
-      UserCKKL.userCreationDate.getTime,
-      initialUserState,
-      initialUserState,
-      DefaultPolicy,
-      DefaultResourcesMap,
-      new Accounting{},
-      Just(clog)
-    )
-    
-    clog.debug("userStateM = %s".format(userStateM))
-    userStateM.forFailed { failed ⇒
-      clog.error(failed)
-      failed.exception.printStackTrace()
-      NoVal
-    }
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
+
+    showUserState(clog, userState)
+
+    clog.end()
   }
 }
\ No newline at end of file