WIP Resource event handling
authorChristos KK Loverdos <loverdos@gmail.com>
Thu, 7 Jun 2012 14:03:59 +0000 (17:03 +0300)
committerChristos KK Loverdos <loverdos@gmail.com>
Thu, 7 Jun 2012 14:03:59 +0000 (17:03 +0300)
23 files changed:
src/main/resources/policy.yaml
src/main/scala/gr/grnet/aquarium/Aquarium.scala
src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
src/main/scala/gr/grnet/aquarium/computation/BillingMonthInfo.scala
src/main/scala/gr/grnet/aquarium/computation/Chargeslot.scala [moved from src/main/scala/gr/grnet/aquarium/logic/accounting/Chargeslot.scala with 98% similarity]
src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala [deleted file]
src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala [moved from src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala with 76% similarity]
src/main/scala/gr/grnet/aquarium/computation/UserState.scala
src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala
src/main/scala/gr/grnet/aquarium/computation/UserStateWorker.scala
src/main/scala/gr/grnet/aquarium/computation/data/LatestResourceEventsSnapshot.scala
src/main/scala/gr/grnet/aquarium/computation/reason/UserStateChangeReason.scala
src/main/scala/gr/grnet/aquarium/converter/JsonConversions.scala
src/main/scala/gr/grnet/aquarium/event/model/EventModel.scala
src/main/scala/gr/grnet/aquarium/event/model/NewWalletEntry.scala
src/main/scala/gr/grnet/aquarium/event/model/resource/ResourceEventModel.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/Policy.scala
src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCostPolicy.scala
src/main/scala/gr/grnet/aquarium/store/memory/MemStore.scala
src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBEventModel.scala
src/main/scala/gr/grnet/aquarium/util/date/TimeHelpers.scala
src/test/scala/gr/grnet/aquarium/computation/TimeslotComputationsTest.scala [moved from src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala with 75% similarity]
src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala

index f909b1a..5930c15 100644 (file)
@@ -1,30 +1,21 @@
 aquariumpolicy:
   resources:
     - resource:
-      name: bandwidthup
-      unit: MB/hr
-      complex: false
-      costpolicy: discrete
-    - resource:
-      name: bandwidthdown
-      unit: MB/hr
+      name: bandwidth
+      unit: MB/Hr
       complex: false
       costpolicy: discrete
     - resource:
       name: vmtime
-      unit: Hour
+      unit: Hr
       complex: true
       costpolicy: onoff
+      descriminatorfield: vmid
     - resource:
       name: diskspace
       unit: MB/hr
       complex: false
       costpolicy: continuous
-    - resource:
-      name: refills
-      unit: credits
-      complex: false
-      costpolicy: once
 
   implicitvars:
     - price
@@ -33,27 +24,21 @@ aquariumpolicy:
   algorithms:
     - algorithm:
       name: default
-      bandwidthup: $price times $volume
-      bandwidthdown: $price times $volume
-      vmtime: $price times $volume
-      diskspace: $price times $volume
-      bookpages: $price times $volume
-      refills: $price times $volume
+      bandwidth: function bandwidth() {return 1;}
+      vmtime: function vmtime() {return 1;}
+      diskspace: function diskspace() {return 1;}
       effective:
         from: 0
-  
+
   pricelists:
-    - pricelist: 
+    - pricelist:
       name: default
-      bandwidthup: 0.01
-      bandwidthdown: 0.02
-      vmtime: 0.1
-      diskspace: 0.05
-      bookpages: 0.1
-      refills: 1
+      bandwidth: 0.01
+      vmtime: 0.01
+      diskspace: 0.01
       effective:
         from: 0
-  
+
   creditplans:
     - creditplan:
       name: default
@@ -68,4 +53,3 @@ aquariumpolicy:
       algorithm: default
       pricelist: default
       creditplan: default
-
index a683a3d..8654577 100644 (file)
@@ -60,7 +60,7 @@ import gr.grnet.aquarium.logic.accounting.Policy
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>.
  */
-final class Aquarium(val props: Props) extends Lifecycle with Loggable {
+final class Aquarium(val props: Props) extends Lifecycle with Loggable { aquariumSelf ⇒
   import Aquarium.Keys
 
   private[this] val _isStopping = new AtomicBoolean(false)
@@ -125,8 +125,7 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable {
 
   private[this] lazy val _algorithmCompiler: CostPolicyAlgorithmCompiler = SimpleCostPolicyAlgorithmCompiler
 
-  // FIXME: () ⇒ this ?
-  private[this] lazy val _userStateComputations = new UserStateComputations(() ⇒ this)
+  private[this] lazy val _userStateComputations = new UserStateComputations(aquariumSelf)
 
   private[this] lazy val _actorProvider = newInstance[RoleableActorProviderService](props(Keys.actor_provider_class))
 
@@ -399,6 +398,11 @@ final class Aquarium(val props: Props) extends Lifecycle with Loggable {
     // FIXME: Where is the mapping?
     10000.0
   }
+
+  def defaultInitialUserRole: String = {
+    // FIXME: Read from properties?
+    "default"
+  }
   
   def withStoreProviderClass[C <: StoreProvider](spc: Class[C]): Aquarium = {
     val map = this.props.map
index fcb5b4a..7c16fb8 100644 (file)
@@ -46,10 +46,10 @@ import gr.grnet.aquarium.actor.message.config.{InitializeUserState, ActorProvide
 import gr.grnet.aquarium.computation.{BillingMonthInfo, UserStateBootstrappingData, UserState}
 import gr.grnet.aquarium.util.date.TimeHelpers
 import gr.grnet.aquarium.event.model.im.IMEventModel
-import gr.grnet.aquarium.{AquariumException, Aquarium}
 import gr.grnet.aquarium.actor.message.{GetUserStateResponse, GetUserBalanceResponseData, GetUserBalanceResponse, GetUserStateRequest, GetUserBalanceRequest}
 import gr.grnet.aquarium.util.{LogHelpers, shortClassNameOf, shortNameOfClass, shortNameOfType}
-import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, InitialUserActorSetup, UserStateChangeReason, IMEventArrival, InitialUserStateSetup}
+import gr.grnet.aquarium.computation.reason.{RealtimeBillingCalculation, NoSpecificChangeReason, InitialUserActorSetup, UserStateChangeReason, IMEventArrival, InitialUserStateSetup}
+import gr.grnet.aquarium.{AquariumInternalError, AquariumException, Aquarium}
 
 /**
  *
@@ -60,6 +60,7 @@ class UserActor extends ReflectiveRoleableActor {
   private[this] var _userID: String = "<?>"
   private[this] var _imState: IMStateSnapshot = _
   private[this] var _userState: UserState = _
+  private[this] var _latestResourceEventOccurredMillis = TimeHelpers.nowMillis() // some valid datetime
 
   self.lifeCycle = Temporary
 
@@ -84,7 +85,7 @@ class UserActor extends ReflectiveRoleableActor {
   private[this] def userStateComputations = aquarium.userStateComputations
 
   private[this] def _timestampTheshold = {
-    aquarium.props.getLong(Aquarium.Keys.user_state_timestamp_threshold).getOr(10000)
+    aquarium.props.getLong(Aquarium.Keys.user_state_timestamp_threshold).getOr(1000L * 60 * 5 /* 5 minutes */)
   }
 
   private[this] def haveUserState = {
@@ -164,11 +165,13 @@ class UserActor extends ReflectiveRoleableActor {
       aquarium.initialBalanceForRole(initialRole, userCreationMillis)
     )
 
-    val userState = userStateComputations.doFullMonthlyBilling(
+    val userState = userStateComputations.doBillingForMonth(
       userStateBootstrap,
       BillingMonthInfo.fromMillis(TimeHelpers.nowMillis()),
+      false,
+      TimeHelpers.nowMillis(),
       aquarium.currentResourcesMap,
-      InitialUserStateSetup(),
+      InitialUserStateSetup(None),
       None
     )
 
@@ -236,7 +239,7 @@ class UserActor extends ReflectiveRoleableActor {
 
     if(!haveIMState) {
       // This is an error. Should have been initialized from somewhere ...
-      throw new AquariumException("Got %s while being uninitialized".format(processEvent))
+      throw new AquariumInternalError("Got %s while uninitialized".format(processEvent))
     }
 
     if(this._imState.latestIMEvent.id == imEvent.id) {
@@ -245,6 +248,7 @@ class UserActor extends ReflectiveRoleableActor {
       // already been loaded from DB!
       INFO("Ignoring first %s just after %s birth", imEvent.toDebugString, shortClassNameOf(this))
       logSeparator()
+
       return
     }
 
@@ -271,7 +275,6 @@ class UserActor extends ReflectiveRoleableActor {
     }
 
     DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
-
     logSeparator()
   }
 
@@ -282,8 +285,63 @@ class UserActor extends ReflectiveRoleableActor {
       // This means the user has not been activated. So, we do not process any resource event
       DEBUG("Not processing %s", rcEvent.toJsonString)
       logSeparator()
+
+      return
+    }
+
+    this._userState.findLatestResourceEventID match {
+      case Some(id) ⇒
+        if(id == rcEvent.id) {
+          INFO("Ignoring first %s just after %s birth", rcEvent.toDebugString, shortClassNameOf(this))
+          logSeparator()
+
+          return
+        }
+
+      case _ ⇒
+    }
+
+    val now = TimeHelpers.nowMillis()
+    val dt  = now - this._latestResourceEventOccurredMillis
+    val belowThreshold = dt <= _timestampTheshold
+
+    if(belowThreshold) {
+      this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
+
+      DEBUG("Below threshold (%s ms). Not processing %s", this._timestampTheshold, rcEvent.toJsonString)
       return
     }
+
+    val userID = this._userID
+    val userCreationMillis = this._imState.userCreationMillis.get
+    val initialRole = this._imState.roleHistory.firstRoleName.getOrElse(aquarium.defaultInitialUserRole)
+    val initialAgreement = aquarium.initialAgreementForRole(initialRole, userCreationMillis)
+    val initialCredits   = aquarium.initialBalanceForRole(initialRole, userCreationMillis)
+    val userStateBootstrap = UserStateBootstrappingData(
+      userID,
+      userCreationMillis,
+      initialRole,
+      initialAgreement,
+      initialCredits
+    )
+    val billingMonthInfoNow =BillingMonthInfo.fromMillis(now)
+    val currentResourcesMap = aquarium.currentResourcesMap
+    val calculationReason = RealtimeBillingCalculation(None, now)
+
+    DEBUG("Using %s", currentResourcesMap)
+
+    this._userState = aquarium.userStateComputations.doBillingForMonth(
+      userStateBootstrap,
+      billingMonthInfoNow,
+      false,
+      now,
+      currentResourcesMap,
+      calculationReason
+    )
+
+    this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis
+
+    DEBUG("New %s = %s", shortClassNameOf(this._userState), this._userState)
   }
 
   def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = {
@@ -317,7 +375,7 @@ class UserActor extends ReflectiveRoleableActor {
         }
 
       case (false, true) ⇒
-        // (no IMState, have UserState
+        // (no IMState, have UserState)
         // A bit ridiculous situation
         self reply GetUserBalanceResponse(Left("Unknown user %s [AQU-BAL-0004]".format(userID)), 404/*Not found*/)
 
index 6b2bcf9..69ca62e 100644 (file)
@@ -35,8 +35,8 @@
 
 package gr.grnet.aquarium.computation
 
-import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.util.shortClassNameOf
+import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc}
 
 /**
  * Provides information about the billing month and related calculation utilities.
@@ -33,7 +33,7 @@
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.logic.accounting
+package gr.grnet.aquarium.computation
 
 import gr.grnet.aquarium.util._
 import gr.grnet.aquarium.util.date.MutableDateCalc
diff --git a/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala b/src/main/scala/gr/grnet/aquarium/computation/NewUserState.scala
deleted file mode 100644 (file)
index 34086bc..0000000
+++ /dev/null
@@ -1,85 +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.computation
-
-import gr.grnet.aquarium.computation.data.{AgreementHistory, RoleHistory, AgreementHistoryItem}
-import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
-
-
-/**
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-
-case class NewUserState(
-  isInitial: Boolean,
-  userID: String,
-  userCreationMillis: Long,
-  stateReferenceMillis: Long, // The time this state refers to
-  totalCredits: Double,
-  roleHistory: RoleHistory,
-  agreementHistory: AgreementHistory,
-  latestResourceEventID: String
-)
-
-object NewUserState {
-  def fromJson(json: String): NewUserState = {
-    StdConverters.AllConverters.convertEx[NewUserState](JsonTextFormat(json))
-  }
-
-  object JsonNames {
-    final val _id = "_id"
-    final val userID = "userID"
-  }
-
-  def createInitialUserState(userID: String,
-                             credits: Double,
-                             isActive: Boolean,
-                             role: String,
-                             agreement: String,
-                             userCreationMillis: Long): NewUserState = {
-    NewUserState(
-      true,
-      userID,
-      userCreationMillis,
-      userCreationMillis,
-      credits,
-      RoleHistory.initial(role, userCreationMillis),
-      AgreementHistory.initial(agreement, userCreationMillis),
-     ""
-    )
-  }
-}
\ No newline at end of file
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.logic.accounting
+package gr.grnet.aquarium.computation
 
-import algorithm.CostPolicyAlgorithmCompiler
-import dsl._
 import collection.immutable.SortedMap
 import com.ckkloverdos.maybe.{NoVal, Maybe, Just}
 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
@@ -44,6 +42,8 @@ import gr.grnet.aquarium.store.PolicyStore
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.logic.accounting.algorithm.CostPolicyAlgorithmCompiler
+import gr.grnet.aquarium.logic.accounting.dsl.{DSL, DSLResourcesMap, DSLPolicy, DSLResource, DSLPriceList, DSLAlgorithm, DSLAgreement, Timeslot, DSLUtils}
 
 /**
  * Methods for converting accounting events to wallet entries.
@@ -51,9 +51,10 @@ import gr.grnet.aquarium.event.model.resource.ResourceEventModel
  * @author Georgios Gousios <gousiosg@gmail.com>
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-trait Accounting extends Loggable {
-  // TODO: favour composition over inheritance until we decide what to do with DSLUtils (and Accounting).
+trait TimeslotComputations extends Loggable {
+  // TODO: favour composition over inheritance until we decide what to do with DSLUtils (and TimeslotComputations).
   protected val dslUtils = new DSLUtils {}
+
   /**
    * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
    *
@@ -68,20 +69,20 @@ trait Accounting extends Loggable {
                                            agreementTimeslots: List[Timeslot],
                                            clogM: Maybe[ContextualLogger] = NoVal): List[Timeslot] = {
 
-//    val clog = ContextualLogger.fromOther(clogM, logger, "splitTimeslotByPoliciesAndAgreements()")
-//    clog.begin()
+    //    val clog = ContextualLogger.fromOther(clogM, logger, "splitTimeslotByPoliciesAndAgreements()")
+    //    clog.begin()
 
     // Align policy and agreement validity timeslots to the referenceTimeslot
-    val alignedPolicyTimeslots    = referenceTimeslot.align(policyTimeslots)
+    val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
     val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
 
-//    clog.debug("referenceTimeslot = %s", referenceTimeslot)
-//    clog.debugSeq("alignedPolicyTimeslots", alignedPolicyTimeslots, 0)
-//    clog.debugSeq("alignedAgreementTimeslots", alignedAgreementTimeslots, 0)
+    //    clog.debug("referenceTimeslot = %s", referenceTimeslot)
+    //    clog.debugSeq("alignedPolicyTimeslots", alignedPolicyTimeslots, 0)
+    //    clog.debugSeq("alignedAgreementTimeslots", alignedAgreementTimeslots, 0)
 
     val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
-//    clog.debugSeq("result", result, 1)
-//    clog.end()
+    //    clog.debugSeq("result", result, 1)
+    //    clog.end()
     result
   }
 
@@ -94,7 +95,7 @@ trait Accounting extends Loggable {
   def resolveEffectiveAlgorithmsAndPriceLists(alignedTimeslot: Timeslot,
                                               agreement: DSLAgreement,
                                               clogOpt: Option[ContextualLogger] = None):
-          (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
+  (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
 
     val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveAlgorithmsAndPriceLists()")
 
@@ -118,13 +119,13 @@ trait Accounting extends Loggable {
                                 clogOpt: Option[ContextualLogger] = None): List[Chargeslot] = {
 
     val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
-//    clog.begin()
+    //    clog.begin()
 
     val policyTimeslots = policiesByTimeslot.keySet
     val agreementTimeslots = agreementNamesByTimeslot.keySet
 
-//    clog.debugMap("policiesByTimeslot", policiesByTimeslot, 1)
-//    clog.debugMap("agreementNamesByTimeslot", agreementNamesByTimeslot, 1)
+    //    clog.debugMap("policiesByTimeslot", policiesByTimeslot, 1)
+    //    clog.debugMap("agreementNamesByTimeslot", agreementNamesByTimeslot, 1)
 
     def getPolicy(ts: Timeslot): DSLPolicy = {
       policiesByTimeslot.find(_._1.contains(ts)).get._2
@@ -134,27 +135,27 @@ trait Accounting extends Loggable {
     }
 
     // 1. Round ONE: split time according to overlapping policies and agreements.
-//    clog.begin("ROUND 1")
+    //    clog.begin("ROUND 1")
     val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
-//    clog.debugSeq("alignedTimeslots", alignedTimeslots, 1)
-//    clog.end("ROUND 1")
+    //    clog.debugSeq("alignedTimeslots", alignedTimeslots, 1)
+    //    clog.end("ROUND 1")
 
     // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
     //    fine-grained timeslots according to applicable algorithms.
     //    Then pack the info into charge slots.
-//    clog.begin("ROUND 2")
+    //    clog.begin("ROUND 2")
     val allChargeslots = for {
       alignedTimeslot <- alignedTimeslots
     } yield {
-//      val alignedTimeslotMsg = "alignedTimeslot = %s".format(alignedTimeslot)
-//      clog.begin(alignedTimeslotMsg)
+      //      val alignedTimeslotMsg = "alignedTimeslot = %s".format(alignedTimeslot)
+      //      clog.begin(alignedTimeslotMsg)
 
       val dslPolicy = getPolicy(alignedTimeslot)
-//      clog.debug("dslPolicy = %s", dslPolicy)
+      //      clog.debug("dslPolicy = %s", dslPolicy)
       val agreementName = getAgreementName(alignedTimeslot)
-//      clog.debug("agreementName = %s", agreementName)
+      //      clog.debug("agreementName = %s", agreementName)
       val agreementOpt = dslPolicy.findAgreement(agreementName)
-//      clog.debug("agreementOpt = %s", agreementOpt)
+      //      clog.debug("agreementOpt = %s", agreementOpt)
 
       agreementOpt match {
         case None ⇒
@@ -176,19 +177,19 @@ trait Accounting extends Loggable {
           val chargeslots = for {
             finegrainedTimeslot <- finegrainedTimeslots
           } yield {
-//            val finegrainedTimeslotMsg = "finegrainedTimeslot = %s".format(finegrainedTimeslot)
-//            clog.begin(finegrainedTimeslotMsg)
+            //            val finegrainedTimeslotMsg = "finegrainedTimeslot = %s".format(finegrainedTimeslot)
+            //            clog.begin(finegrainedTimeslotMsg)
 
             val dslAlgorithm = algorithmByTimeslot(finegrainedTimeslot) // TODO: is this correct?
-//            clog.debug("dslAlgorithm = %s", dslAlgorithm)
-//            clog.debugMap("dslAlgorithm.algorithms", dslAlgorithm.algorithms, 1)
+            //            clog.debug("dslAlgorithm = %s", dslAlgorithm)
+            //            clog.debugMap("dslAlgorithm.algorithms", dslAlgorithm.algorithms, 1)
             val dslPricelist = pricelistByTimeslot(finegrainedTimeslot) // TODO: is this correct?
-//            clog.debug("dslPricelist = %s", dslPricelist)
-//            clog.debug("dslResource = %s", dslResource)
+            //            clog.debug("dslPricelist = %s", dslPricelist)
+            //            clog.debug("dslResource = %s", dslResource)
             val algorithmDefOpt = dslAlgorithm.algorithms.get(dslResource)
-//            clog.debug("algorithmDefOpt = %s", algorithmDefOpt)
+            //            clog.debug("algorithmDefOpt = %s", algorithmDefOpt)
             val priceUnitOpt = dslPricelist.prices.get(dslResource)
-//            clog.debug("priceUnitOpt = %s", priceUnitOpt)
+            //            clog.debug("priceUnitOpt = %s", priceUnitOpt)
 
             val chargeslot = (algorithmDefOpt, priceUnitOpt) match {
               case (None, None) ⇒
@@ -207,20 +208,20 @@ trait Accounting extends Loggable {
                 Chargeslot(finegrainedTimeslot.from.getTime, finegrainedTimeslot.to.getTime, algorithmDefinition, priceUnit)
             }
 
-//            clog.end(finegrainedTimeslotMsg)
+            //            clog.end(finegrainedTimeslotMsg)
             chargeslot
           }
 
-//          clog.end(alignedTimeslotMsg)
+          //          clog.end(alignedTimeslotMsg)
           chargeslots.toList
       }
     }
-//    clog.end("ROUND 2")
+    //    clog.end("ROUND 2")
 
 
     val result = allChargeslots.flatten
-//    clog.debugSeq("result", allChargeslots, 1)
-//    clog.end()
+    //    clog.debugSeq("result", allChargeslots, 1)
+    //    clog.end()
     result
   }
 
@@ -241,29 +242,29 @@ trait Accounting extends Loggable {
                              clogOpt: Option[ContextualLogger] = None): (Timeslot, List[Chargeslot]) = {
 
     val clog = ContextualLogger.fromOther(clogOpt, logger, "computeFullChargeslots()")
-//    clog.begin()
+    //    clog.begin()
 
     val occurredDate = currentResourceEvent.occurredDate
     val occurredMillis = currentResourceEvent.occurredMillis
     val costPolicy = dslResource.costPolicy
 
-    val dsl = new DSL{}
+    val dsl = new DSL {}
     val (referenceTimeslot, relevantPolicies, previousValue) = costPolicy.needsPreviousEventForCreditAndAmountCalculation match {
       // We need a previous event
       case true ⇒
         previousResourceEventOpt match {
           // We have a previous event
           case Some(previousResourceEvent) ⇒
-//            clog.debug("Have previous event")
-//            clog.debug("previousValue = %s", previousResourceEvent.value)
+            //            clog.debug("Have previous event")
+            //            clog.debug("previousValue = %s", previousResourceEvent.value)
 
             val referenceTimeslot = Timeslot(previousResourceEvent.occurredDate, occurredDate)
-//            clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
+            //            clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
 
             // all policies within the interval from previous to current resource event
-//            clog.debug("Calling policyStore.loadAndSortPoliciesWithin(%s)", referenceTimeslot)
+            //            clog.debug("Calling policyStore.loadAndSortPoliciesWithin(%s)", referenceTimeslot)
             val relevantPolicies = policyStore.loadAndSortPoliciesWithin(referenceTimeslot.from.getTime, referenceTimeslot.to.getTime, dsl)
-//            clog.debugMap("==> relevantPolicies", relevantPolicies, 0)
+            //            clog.debugMap("==> relevantPolicies", relevantPolicies, 0)
 
             (referenceTimeslot, relevantPolicies, previousResourceEvent.value)
 
@@ -271,23 +272,23 @@ trait Accounting extends Loggable {
           case None ⇒
             throw new AquariumException(
               "Unable to charge. No previous event given for %s".
-                format(currentResourceEvent.toDebugString()))
+                format(currentResourceEvent.toDebugString))
         }
 
       // We do not need a previous event
       case false ⇒
         // ... so we cannot compute timedelta from a previous event, there is just one chargeslot
         // referring to (almost) an instant in time
-//        clog.debug("DO NOT have previous event")
+        //        clog.debug("DO NOT have previous event")
         val previousValue = costPolicy.getResourceInstanceUndefinedAmount
-//        clog.debug("previousValue = costPolicy.getResourceInstanceUndefinedAmount = %s", previousValue)
+        //        clog.debug("previousValue = costPolicy.getResourceInstanceUndefinedAmount = %s", previousValue)
 
         val referenceTimeslot = Timeslot(new MutableDateCalc(occurredDate).goPreviousMilli.toDate, occurredDate)
-//        clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
+        //        clog.debug("referenceTimeslot = %s".format(referenceTimeslot))
 
-//        clog.debug("Calling policyStore.loadValidPolicyEntryAt(%s)", new MutableDateCalc(occurredMillis))
+        //        clog.debug("Calling policyStore.loadValidPolicyEntryAt(%s)", new MutableDateCalc(occurredMillis))
         val relevantPolicyOpt = policyStore.loadValidPolicyAt(occurredMillis, dsl)
-//        clog.debug("  ==> relevantPolicyM = %s", relevantPolicyM)
+        //        clog.debug("  ==> relevantPolicyM = %s", relevantPolicyM)
 
         val relevantPolicies = relevantPolicyOpt match {
           case Some(relevantPolicy) ⇒
@@ -309,7 +310,7 @@ trait Accounting extends Loggable {
     )
 
     val fullChargeslots = initialChargeslots.map {
-      case chargeslot @ Chargeslot(startMillis, stopMillis, algorithmDefinition, unitPrice, _) ⇒
+      case chargeslot@Chargeslot(startMillis, stopMillis, algorithmDefinition, unitPrice, _) ⇒
         val execAlgorithm = algorithmCompiler.compile(algorithmDefinition)
         val valueMap = costPolicy.makeValueMap(
           oldCredits,
@@ -321,7 +322,7 @@ trait Accounting extends Loggable {
           unitPrice
         )
 
-//              clog.debug("execAlgorithm = %s", execAlgorithm)
+        //              clog.debug("execAlgorithm = %s", execAlgorithm)
         clog.debugMap("valueMap", valueMap, 1)
 
         // This is it
@@ -339,9 +340,9 @@ trait Accounting extends Loggable {
    * and pricelists can have different effectivity periods, this method
    * examines them and splits them as necessary.
    */
-  private[logic] def splitChargeChunks(alg: SortedMap[Timeslot, DSLAlgorithm],
-                                       price: SortedMap[Timeslot, DSLPriceList]) :
-    (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
+  private[computation] def splitChargeChunks(alg: SortedMap[Timeslot, DSLAlgorithm],
+                                       price: SortedMap[Timeslot, DSLPriceList]):
+  (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = {
 
     val zipped = alg.keySet.zip(price.keySet)
 
@@ -353,7 +354,7 @@ trait Accounting extends Loggable {
 
         assert(algTimeslot.from == priTimeslot.from)
 
-        if (algTimeslot.endsAfter(priTimeslot)) {
+        if(algTimeslot.endsAfter(priTimeslot)) {
           val slices = algTimeslot.slice(priTimeslot.to)
           val algo = alg.get(algTimeslot).get
           val newalg = alg - algTimeslot ++ Map(slices.apply(0) -> algo) ++ Map(slices.apply(1) -> algo)
@@ -376,32 +377,32 @@ trait Accounting extends Loggable {
    * For example, given the timeslots a and b below, split them as shown.
    *
    * a = |****************|
-   *     ^                ^
-   *   a.from            a.to
+   * ^                ^
+   * a.from            a.to
    * b = |*********|
-   *     ^         ^
-   *   b.from     b.to
+   * ^         ^
+   * b.from     b.to
    *
    * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
    */
-  private[logic] def alignTimeslots(a: List[Timeslot],
+  private[computation] def alignTimeslots(a: List[Timeslot],
                                     b: List[Timeslot]): List[Timeslot] = {
 
     def safeTail(foo: List[Timeslot]) = foo match {
-      case Nil       => List()
-      case x :: Nil  => List()
+      case Nil => List()
+      case x :: Nil => List()
       case x :: rest => rest
     }
 
-    if (a.isEmpty) return b
-    if (b.isEmpty) return a
+    if(a.isEmpty) return b
+    if(b.isEmpty) return a
 
-    assert (a.head.from == b.head.from)
+    assert(a.head.from == b.head.from)
 
-    if (a.head.endsAfter(b.head)) {
+    if(a.head.endsAfter(b.head)) {
       val slice = a.head.slice(b.head.to)
       slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
-    } else if (b.head.endsAfter(a.head)) {
+    } else if(b.head.endsAfter(a.head)) {
       val slice = b.head.slice(a.head.to)
       slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
     } else {
index df3c431..be838f3 100644 (file)
 
 package gr.grnet.aquarium.computation
 
-import org.bson.types.ObjectId
-
 import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
 import gr.grnet.aquarium.event.model.NewWalletEntry
 import gr.grnet.aquarium.util.json.JsonSupport
 import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement
 import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason, InitialUserStateSetup}
-import gr.grnet.aquarium.computation.data.{RoleHistory, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementHistory, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot}
+import gr.grnet.aquarium.computation.data.{OwnedResourcesMap, RoleHistory, ResourceInstanceSnapshot, OwnedResourcesSnapshot, AgreementHistory, LatestResourceEventsSnapshot, ImplicitlyIssuedResourceEventsSnapshot}
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
 
 /**
  * A comprehensive representation of the User's state.
@@ -77,7 +76,7 @@ import gr.grnet.aquarium.computation.data.{RoleHistory, ResourceInstanceSnapshot
  *          only those that refer to billing periods (end of billing period).
   * @param lastChangeReason
  *          The [[gr.grnet.aquarium.computation.reason.UserStateChangeReason]] for which the usr state has changed.
- * @param parentUserStateId
+ * @param parentUserStateIDInStore
  *          The `ID` of the parent state. The parent state is the one used as a reference point in order to calculate
  *          this user state.
  * @param _id
@@ -108,7 +107,7 @@ case class UserState(
      * This is set when the user state refers to a full billing period (= month)
      * and is used to cache the user state for subsequent queries.
      */
-    theFullBillingMonth: BillingMonthInfo,
+    theFullBillingMonth: Option[BillingMonthInfo],
 
     /**
      * If this is a state for a full billing month, then keep here the implicit OFF
@@ -148,11 +147,11 @@ case class UserState(
     lastChangeReason: UserStateChangeReason = NoSpecificChangeReason(),
     // The user state we used to compute this one. Normally the (cached)
     // state at the beginning of the billing period.
-    parentUserStateId: Option[String] = None,
+    parentUserStateIDInStore: Option[String] = None,
     _id: String = null
 ) extends JsonSupport {
 
-  def idOpt: Option[String] = _id match {
+  def idInStore: Option[String] = _id match {
     case null ⇒ None
     case _id  ⇒ Some(_id.toString)
   }
@@ -173,42 +172,39 @@ case class UserState(
     ownedResourcesSnapshot.getResourceInstanceAmount(resource, instanceId, defaultValue)
   }
 
-  def copyForResourcesSnapshotUpdate(resource: String,   // resource name
+  def newWithResourcesSnapshotUpdate(resource: String,   // resource name
                                      instanceId: String, // resource instance id
                                      newAmount: Double,
                                      snapshotTime: Long): UserState = {
 
-    val (newResources, _, _) = ownedResourcesSnapshot.computeResourcesSnapshotUpdate(resource, instanceId, newAmount, snapshotTime)
+    val (newResources, _, _) =
+      ownedResourcesSnapshot.computeResourcesSnapshotUpdate(resource, instanceId, newAmount, snapshotTime)
 
     this.copy(
+      isInitial = false,
       ownedResourcesSnapshot = newResources,
       stateChangeCounter = this.stateChangeCounter + 1)
   }
 
-  def copyForChangeReason(changeReason: UserStateChangeReason) = {
+  def newWithChangeReason(changeReason: UserStateChangeReason) = {
     this.copy(
+      isInitial = false,
       lastChangeReason = changeReason,
       stateChangeCounter = this.stateChangeCounter + 1
     )
   }
 
-//  def copyForRoleHistory(newRoleHistory: RoleHistory) = {
-//    this.copy(
-//      roleHistory = newRoleHistory,
-//      stateChangeCounter = this.stateChangeCounter + 1
-//    )
-//  }
+  def resourcesMap: OwnedResourcesMap = {
+    ownedResourcesSnapshot.toResourcesMap
+  }
 
-  def resourcesMap = ownedResourcesSnapshot.toResourcesMap
+  def findLatestResourceEvent: Option[ResourceEventModel] = {
+    latestResourceEventsSnapshot.findTheLatest
+  }
 
-//  def modifyFromIMEvent(imEvent: IMEventModel, snapshotMillis: Long): UserState = {
-//    this.copy(
-//      isInitial = false,
-//      imStateSnapshot = imStateSnapshot.addMostRecentEvent(imEvent),
-//      lastChangeReason = IMEventArrival(imEvent),
-//      occurredMillis = snapshotMillis
-//    )
-//  }
+  def findLatestResourceEventID: Option[String] = {
+    latestResourceEventsSnapshot.findTheLatestID
+  }
 
 //  def toShortString = "UserState(%s, %s, %s, %s, %s)".format(
 //    userId,
@@ -237,18 +233,23 @@ object UserState {
     }
   }
 
-  def createInitialUserState(userID: String,
-                             userCreationMillis: Long,
-                             totalCredits: Double,
-                             initialRole: String,
-                             initialAgreement: String) = {
+  def createInitialUserState(
+      userID: String,
+      userCreationMillis: Long,
+      occurredMillis: Long,
+      totalCredits: Double,
+      initialRole: String,
+      initialAgreement: String,
+      calculationReason: UserStateChangeReason = InitialUserStateSetup(None)
+  ) = {
+
     UserState(
       true,
       userID,
       userCreationMillis,
       0L,
       false,
-      null,
+      None,
       ImplicitlyIssuedResourceEventsSnapshot.Empty,
       LatestResourceEventsSnapshot.Empty,
       0L,
@@ -257,28 +258,25 @@ object UserState {
       AgreementHistory.initial(initialAgreement, userCreationMillis),
       OwnedResourcesSnapshot.Empty,
       Nil,
-      userCreationMillis,
-      InitialUserStateSetup()
+      occurredMillis,
+      calculationReason
     )
   }
 
-  def createInitialUserState(usb: UserStateBootstrappingData): UserState = {
+  def createInitialUserStateFromBootstrap(
+      usb: UserStateBootstrappingData,
+      occurredMillis: Long,
+      calculationReason: UserStateChangeReason
+  ): UserState = {
+
     createInitialUserState(
       usb.userID,
       usb.userCreationMillis,
+      occurredMillis,
       usb.initialCredits,
       usb.initialRole,
-      usb.initialAgreement
-    )
-  }
-
-  def createInitialUserStateFrom(us: UserState): UserState = {
-    createInitialUserState(
-      us.userID,
-      us.userCreationMillis,
-      us.totalCredits,
-      us.roleHistory.firstRoleName.getOrElse("default"),          // FIXME What is the default?
-      us.agreementHistory.firstAgreementName.getOrElse("default") // FIXME What is the default?
+      usb.initialAgreement,
+      calculationReason
     )
   }
 }
index c5b5925..bd3262d 100644 (file)
@@ -39,29 +39,29 @@ import scala.collection.mutable
 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
 import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc}
 import gr.grnet.aquarium.logic.accounting.dsl.DSLResourcesMap
-import gr.grnet.aquarium.logic.accounting.Accounting
 import gr.grnet.aquarium.computation.data._
-import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, UserStateChangeReason}
 import gr.grnet.aquarium.event.model.NewWalletEntry
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.{Aquarium, AquariumInternalError, AquariumException}
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
+import gr.grnet.aquarium.computation.reason.{MonthlyBillingCalculation, InitialUserStateSetup, UserStateChangeReason}
 
 /**
  *
  * @author Christos KK Loverdos <loverdos@gmail.com>
  */
-class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
+final class UserStateComputations(_aquarium: => Aquarium) extends Loggable {
 
-  protected lazy val aquarium           = _aquarium()
-  protected lazy val storeProvider      = aquarium.storeProvider
-  protected lazy val accounting         = new Accounting {}
-  protected lazy val algorithmCompiler  = aquarium.algorithmCompiler
-  protected lazy val policyStore        = storeProvider.policyStore
-  protected lazy val userStateStore     = storeProvider.userStateStore
-  protected lazy val resourceEventStore = storeProvider.resourceEventStore
+  lazy val aquarium = _aquarium
+  lazy val storeProvider        = aquarium.storeProvider
+  lazy val timeslotComputations = new TimeslotComputations {}
+  lazy val algorithmCompiler    = aquarium.algorithmCompiler
+  lazy val policyStore          = storeProvider.policyStore
+  lazy val userStateStore       = storeProvider.userStateStore
+  lazy val resourceEventStore   = storeProvider.resourceEventStore
 
   def findUserStateAtEndOfBillingMonth(userStateBootstrap: UserStateBootstrappingData,
                                        billingMonthInfo: BillingMonthInfo,
+                                       billingTimeMillis: Long,
                                        defaultResourcesMap: DSLResourcesMap,
                                        calculationReason: UserStateChangeReason,
                                        clogOpt: Option[ContextualLogger] = None): UserState = {
@@ -76,8 +76,9 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
       doFullMonthlyBilling(
         userStateBootstrap,
         billingMonthInfo,
+        billingTimeMillis,
         defaultResourcesMap,
-        calculationReason,
+        MonthlyBillingCalculation(calculationReason, billingMonthInfo),
         Some(clog))
     }
 
@@ -91,11 +92,14 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
       // If the user did not exist for this billing month, piece of cake
       clog.debug("User did not exist before %s", userCreationDateCalc)
 
-      // NOTE: Reason here will be: InitialUserStateSetup$
-      val initialUserState0 = UserState.createInitialUserState(userStateBootstrap)
+      val initialUserState0 = UserState.createInitialUserStateFromBootstrap(
+        userStateBootstrap,
+        TimeHelpers.nowMillis(),
+        InitialUserStateSetup(Some(calculationReason)) // we record the originating calculation reason
+      )
       val initialUserState1 = userStateStore.insertUserState(initialUserState0)
 
-      clog.debug("Returning INITIAL state [_id=%s] %s".format(initialUserState1._id, initialUserState1))
+      clog.debug("Initial state %s".format(initialUserState1))
       clog.end()
 
       return initialUserState1
@@ -129,7 +133,7 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
           // ZERO, we are OK!
           case 0 ⇒
             // NOTE: Keep the caller's calculation reason
-            latestUserState.copyForChangeReason(calculationReason)
+            latestUserState.newWithChangeReason(calculationReason)
 
           // We had more, so must recompute
           case n if n > 0 ⇒
@@ -150,7 +154,7 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
 
   //+ Utility methods
   protected def rcDebugInfo(rcEvent: ResourceEventModel) = {
-    rcEvent.toDebugString(false)
+    rcEvent.toDebugString
   }
   //- Utility methods
 
@@ -218,7 +222,7 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
             val alltimeAgreements = _workingUserState.agreementHistory.agreementNamesByTimeslot
 
             //              clog.debug("Computing full chargeslots")
-            val (referenceTimeslot, fullChargeslots) = accounting.computeFullChargeslots(
+            val (referenceTimeslot, fullChargeslots) = timeslotComputations.computeFullChargeslots(
               previousResourceEventOpt,
               currentResourceEvent,
               oldCredits,
@@ -319,12 +323,30 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
     _workingUserState
   }
 
-
   def doFullMonthlyBilling(userStateBootstrap: UserStateBootstrappingData,
                            billingMonthInfo: BillingMonthInfo,
+                           billingTimeMillis: Long,
                            defaultResourcesMap: DSLResourcesMap,
                            calculationReason: UserStateChangeReason,
                            clogOpt: Option[ContextualLogger] = None): UserState = {
+    doBillingForMonth(
+      userStateBootstrap,
+      billingMonthInfo,
+      true,
+      billingTimeMillis,
+      defaultResourcesMap,
+      calculationReason,
+      clogOpt
+    )
+  }
+
+  def doBillingForMonth(userStateBootstrap: UserStateBootstrappingData,
+                        billingMonthInfo: BillingMonthInfo,
+                        fullBillingMonthState: Boolean, // See UserState#isFullBillingMonthState
+                        billingTimeMillis: Long,           // See UserState$occurredMillis
+                        defaultResourcesMap: DSLResourcesMap,
+                        calculationReason: UserStateChangeReason,
+                        clogOpt: Option[ContextualLogger] = None): UserState = {
 
     val userID = userStateBootstrap.userID
 
@@ -339,6 +361,7 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
     val previousBillingMonthUserState = findUserStateAtEndOfBillingMonth(
       userStateBootstrap,
       billingMonthInfo.previousMonth,
+      billingTimeMillis,
       defaultResourcesMap,
       calculationReason.forBillingMonthInfo(billingMonthInfo.previousMonth),
       clogSome
@@ -346,14 +369,13 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
 
     val startingUserState = previousBillingMonthUserState
 
-
     val billingMonthStartMillis = billingMonthInfo.monthStartMillis
     val billingMonthEndMillis = billingMonthInfo.monthStopMillis
 
     // Keep the working (current) user state. This will get updated as we proceed with billing for the month
     // specified in the parameters.
     // NOTE: The calculation reason is not the one we get from the previous user state but the one our caller specifies
-    var _workingUserState = startingUserState.copyForChangeReason(calculationReason)
+    var _workingUserState = startingUserState.newWithChangeReason(calculationReason)
 
     val userStateWorker = UserStateWorker.fromUserState(_workingUserState, defaultResourcesMap)
 
@@ -411,10 +433,21 @@ class UserStateComputations(_aquarium: () ⇒ Aquarium) extends Loggable {
     val lastUpdateTime = TimeHelpers.nowMillis()
 
     _workingUserState = _workingUserState.copy(
+      isFullBillingMonthState = fullBillingMonthState,
+
+      theFullBillingMonth = if(fullBillingMonthState)
+        Some(billingMonthInfo)
+      else
+        _workingUserState.theFullBillingMonth,
+
       implicitlyIssuedSnapshot = userStateWorker.implicitlyIssuedStartEvents.toImmutableSnapshot(lastUpdateTime),
+
       latestResourceEventsSnapshot = userStateWorker.previousResourceEvents.toImmutableSnapshot(lastUpdateTime),
+
       stateChangeCounter = _workingUserState.stateChangeCounter + 1,
-      parentUserStateId = startingUserState.idOpt,
+
+      parentUserStateIDInStore = startingUserState.idInStore,
+
       newWalletEntries = newWalletEntries.toList
     )
 
index 5c12eb6..c39eecf 100644 (file)
@@ -37,7 +37,6 @@ package gr.grnet.aquarium.computation
 
 import scala.collection.mutable
 import gr.grnet.aquarium.logic.accounting.dsl.DSLResourcesMap
-import gr.grnet.aquarium.logic.accounting.Accounting
 import gr.grnet.aquarium.computation.data.{LatestResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, IgnoredFirstResourceEventsWorker}
 import gr.grnet.aquarium.util.ContextualLogger
 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
index 271ca43..8aa9684 100644 (file)
@@ -58,6 +58,16 @@ case class LatestResourceEventsSnapshot(resourceEvents: List[ResourceEventModel]
     }
     LatestResourceEventsWorker(map)
   }
+
+  def findTheLatest: Option[ResourceEventModel] = {
+    resourceEvents.sortWith {
+      case (ev1, ev2) ⇒ ev1.occurredMillis <= ev2.occurredMillis
+    }.headOption
+  }
+
+  def findTheLatestID = {
+    findTheLatest.map(_.id)
+  }
 }
 
 object LatestResourceEventsSnapshot {
index 37eb501..cc156da 100644 (file)
@@ -127,9 +127,9 @@ object InitialUserStateSetup {
   /**
    * When the user state is initially set up.
    */
-  def apply() = {
+  def apply(parentReason: Option[UserStateChangeReason]) = {
     UserStateChangeReason(
-      None,
+      parentReason,
       None,
       Map(
         UserStateChangeReason.Names.`type` -> `type`,
index bb02803..dd98ec0 100644 (file)
@@ -41,7 +41,6 @@ import ext.JodaTimeSerializers
 
 import gr.grnet.aquarium.util.{makeString, UTF_8_Charset}
 import java.nio.charset.Charset
-import gr.grnet.aquarium.computation.reason.{IMEventArrival, RealtimeBillingCalculation, MonthlyBillingCalculation, NoSpecificChangeReason, InitialUserActorSetup, InitialUserStateSetup}
 
 /**
  * Provides conversion methods from and to JSON.
index bf2b66e..ebd83c8 100644 (file)
@@ -57,7 +57,7 @@ trait EventModel {
    * The ID given to this event if/when persisted to a store.
    * The exact type of the id is store-specific.
    */
-  def storeID: Option[AnyRef] = None
+  def idInStore: Option[AnyRef] = None
 
   def eventVersion: String
 
index 0c85532..12a606e 100644 (file)
@@ -35,7 +35,7 @@
 
 package gr.grnet.aquarium.event.model
 
-import gr.grnet.aquarium.logic.accounting.Chargeslot
+import gr.grnet.aquarium.computation.Chargeslot
 import gr.grnet.aquarium.util.date.MutableDateCalc
 import gr.grnet.aquarium.logic.accounting.dsl.{Timeslot, DSLResource}
 import resource.ResourceEventModel
index af51ede..8a4a3eb 100644 (file)
@@ -110,8 +110,8 @@ trait ResourceEventModel extends ExternalEventModel {
     !isOccurredWithinMillis(billingStartMillis, billingStopMillis)
   }
 
-  def toDebugString(useOnlyInstanceId: Boolean = false): String = {
-    val instanceInfo = if(useOnlyInstanceId) instanceID else "%s::%s".format(resource, instanceID)
+  override def toDebugString: String = {
+    val instanceInfo = "%s::%s".format(resource, instanceID)
     val occurredFormatted = new MutableDateCalc(occurredMillis).toYYYYMMDDHHMMSS
     if(occurredMillis == receivedMillis) {
       "%sEVENT(%s, [%s], %s, %s, %s, %s, %s)".format(
index f79afcb..6b600c5 100644 (file)
@@ -214,4 +214,6 @@ object Policy extends DSL with Loggable {
         acc ++ Map(Timeslot(p.validFrom, p.validTo) -> parse(p.policyYAML))
     }
   }
+
+  Policy.policy
 }
\ No newline at end of file
index 9624cb3..fadf433 100644 (file)
@@ -134,7 +134,7 @@ abstract class DSLCostPolicy(val name: String, val vars: Set[DSLCostPolicyVar])
   def getResourceInstanceUndefinedAmount: Double = -1.0
 
   /**
-   * Get the value that will be used in credit calculation in Accounting.chargeEvents
+   * Get the value that will be used in credit calculation in TimeslotComputations.chargeEvents
    */
   def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
 
index 0795355..5db4519 100644 (file)
@@ -114,10 +114,10 @@ class MemStore extends UserStateStore
   def findLatestUserStateForEndOfBillingMonth(userID: String,
                                               yearOfBillingMonth: Int,
                                               billingMonth: Int): Option[UserState] = {
-    val goodOnes = _userStates.filter { userState ⇒
+    val goodOnes = _userStates.filter(_.theFullBillingMonth.isDefined).filter { userState ⇒
         val f1 = userState.userID == userID
         val f2 = userState.isFullBillingMonthState
-        val bm = userState.theFullBillingMonth
+        val bm = userState.theFullBillingMonth.get
         val f3 = (bm ne null) && {
           bm.year == yearOfBillingMonth && bm.month == billingMonth
         }
index 05382ad..45462e6 100644 (file)
@@ -45,5 +45,5 @@ import gr.grnet.aquarium.event.model.ExternalEventModel
 trait MongoDBEventModel extends ExternalEventModel {
   def _id: String
 
-  override def storeID: Option[String] = Option(_id)
+  override def idInStore: Option[String] = Option(_id)
 }
index dd76c98..5c9a393 100644 (file)
@@ -44,8 +44,10 @@ import java.util.Date
  */
 
 object TimeHelpers {
+  @inline
   def nowMillis() = System.currentTimeMillis()
 
+  @inline
   def nowDate = new Date(nowMillis())
 
   def secDiffOfMillis(ms0: Long, ms1: Long) = (ms1 - ms0).toDouble / 1000.0
  * or implied, of GRNET S.A.
  */
 
-package gr.grnet.aquarium.logic.test
+package gr.grnet.aquarium.computation
 
 import gr.grnet.aquarium.util.TestMethods
 import org.junit.Test
 import java.util.Date
 import junit.framework.Assert._
-import gr.grnet.aquarium.logic.accounting.Accounting
-import gr.grnet.aquarium.logic.accounting.dsl.{DSLUtils, Timeslot}
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.logic.test.DSLTestBase
 
 /**
  * Tests for the methods that do accounting
- * 
+ *
  * @author Georgios Gousios <gousiosg@gmail.com>
  */
-class AccountingTest extends DSLTestBase with Accounting with TestMethods {
+class TimeslotComputationsTest extends DSLTestBase with TimeslotComputations with TestMethods {
   @Test
   def testAlignTimeslots() {
-    var a = List(Timeslot(0,1))
-    var b = List(Timeslot(0,2))
+    var a = List(Timeslot(0, 1))
+    var b = List(Timeslot(0, 2))
     var result = alignTimeslots(a, b)
     assertEquals(2, result.size)
-    assertEquals(result.head, Timeslot(0,1))
-    assertEquals(result.tail.head, Timeslot(1,2))
+    assertEquals(result.head, Timeslot(0, 1))
+    assertEquals(result.tail.head, Timeslot(1, 2))
 
-    a = List(Timeslot(0,10))
-    b = List(Timeslot(0,4), Timeslot(4,12))
+    a = List(Timeslot(0, 10))
+    b = List(Timeslot(0, 4), Timeslot(4, 12))
     result = alignTimeslots(a, b)
     assertEquals(3, result.size)
-    assertEquals(result.head, Timeslot(0,4))
-    assertEquals(result.tail.head, Timeslot(4,10))
-    assertEquals(result.last, Timeslot(10,12))
+    assertEquals(result.head, Timeslot(0, 4))
+    assertEquals(result.tail.head, Timeslot(4, 10))
+    assertEquals(result.last, Timeslot(10, 12))
 
-    a = List(Timeslot(0,1), Timeslot(1,3), Timeslot(3,4))
-    b = List(Timeslot(0,2), Timeslot(2,4))
+    a = List(Timeslot(0, 1), Timeslot(1, 3), Timeslot(3, 4))
+    b = List(Timeslot(0, 2), Timeslot(2, 4))
     result = alignTimeslots(a, b)
     assertEquals(4, result.size)
-    assertEquals(result.head, Timeslot(0,1))
-    assertEquals(result.tail.head, Timeslot(1,2))
-    assertEquals(result.tail.tail.head, Timeslot(2,3))
-    assertEquals(result.last, Timeslot(3,4))
+    assertEquals(result.head, Timeslot(0, 1))
+    assertEquals(result.tail.head, Timeslot(1, 2))
+    assertEquals(result.tail.tail.head, Timeslot(2, 3))
+    assertEquals(result.last, Timeslot(3, 4))
 
     before
     val from = new Date(1322555880000L) //Tue, 29 Nov 2011 10:38:00 EET
-    val to = new Date(1322689082000L)  //Wed, 30 Nov 2011 23:38:02 EET
+    val to = new Date(1322689082000L) //Wed, 30 Nov 2011 23:38:02 EET
     val agr = dsl.findAgreement("complextimeslots").get
     a = dslUtils.resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr).keySet.toList
     b = dslUtils.resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr).keySet.toList
 
     result = alignTimeslots(a, b)
     assertEquals(9, result.size)
-    assertEquals(result.last,  b.last)
+    assertEquals(result.last, b.last)
   }
 
   @Test
   def testSplitChargeChunks() = {
-    before 
+    before
     val from = new Date(1322555880000L) //Tue, 29 Nov 2011 10:38:00 EET
     val to = new Date(1322689082000L) //Wed, 30 Nov 2011 23:38:02 EET
 
index c692b49..3417a6e 100644 (file)
 package gr.grnet.aquarium.user
 
 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.logic.accounting.Policy
 import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
 import gr.grnet.aquarium.simulation._
 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.AquariumException
 import gr.grnet.aquarium.Aquarium.{Instance ⇒ AquariumInstance}
-import gr.grnet.aquarium.computation.{UserStateBootstrappingData, UserState, BillingMonthInfo, UserStateComputations}
 import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, MonthlyBillingCalculation}
+import gr.grnet.aquarium.computation.{TimeslotComputations, UserStateBootstrappingData, UserState, BillingMonthInfo}
+import gr.grnet.aquarium.util.date.{TimeHelpers, MutableDateCalc}
 
 
 /**
@@ -137,8 +136,7 @@ aquariumpolicy:
 
   val DSL = new DSL {}
   val DefaultPolicy = DSL parse PolicyYAML
-  val DefaultAccounting = new Accounting{}
-  
+
   val DefaultAlgorithm = new ExecutableCostPolicyAlgorithm {
     def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
       hrs(timeDelta) * oldTotalAmount * ContinuousPriceUnit
@@ -270,12 +268,12 @@ aquariumpolicy:
   private[this]
   def showUserState(clog: ContextualLogger, userState: UserState) {
     val id = userState._id
-    val parentId = userState.parentUserStateId
+    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())
+    val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
+    val latestResourceEvents = userState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
 
     clog.debug("_id = %s", id)
     clog.debug("parentId = %s", parentId)
@@ -292,7 +290,7 @@ aquariumpolicy:
     clog.begin("Events by OccurredMillis")
     clog.withIndent {
       for(event <- UserCKKL.myResourceEventsByOccurredDate) {
-        clog.debug(event.toDebugString())
+        clog.debug(event.toDebugString)
       }
     }
     clog.end("Events by OccurredMillis")
@@ -300,10 +298,17 @@ aquariumpolicy:
   }
 
   private[this]
-  def doFullMonthlyBilling(clog: ContextualLogger, billingMonthInfo: BillingMonthInfo) = {
-    Computations.doFullMonthlyBilling(
+  def doFullMonthlyBilling(
+      clog: ContextualLogger,
+      billingMonthInfo: BillingMonthInfo,
+      isFullBillingMonthState: Boolean,
+      billingTimeMillis: Long) = {
+
+    Computations.doBillingForMonth(
       UserStateBootstrap,
       billingMonthInfo,
+      isFullBillingMonthState,
+      billingTimeMillis,
       DefaultResourcesMap,
       MonthlyBillingCalculation(NoSpecificChangeReason(), billingMonthInfo),
       Some(clog)
@@ -348,7 +353,7 @@ aquariumpolicy:
 
     showResourceEvents(clog)
 
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
 
     showUserState(clog, userState)
 
@@ -377,7 +382,7 @@ aquariumpolicy:
 
     showResourceEvents(clog)
 
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
 
     showUserState(clog, userState)
 
@@ -407,7 +412,7 @@ aquariumpolicy:
 
     showResourceEvents(clog)
 
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
 
     showUserState(clog, userState)
 
@@ -458,7 +463,7 @@ aquariumpolicy:
 
     clog.debugMap("DefaultResourcesMap", DefaultResourcesMap.map, 1)
 
-    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+    val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
 
     showUserState(clog, userState)