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
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
algorithm: default
pricelist: default
creditplan: default
-
*
* @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)
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))
// 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
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}
/**
*
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
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 = {
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
)
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) {
// already been loaded from DB!
INFO("Ignoring first %s just after %s birth", imEvent.toDebugString, shortClassNameOf(this))
logSeparator()
+
return
}
}
DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState)
-
logSeparator()
}
// 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 = {
}
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*/)
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.
* 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
+++ /dev/null
-/*
- * 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}
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.
* @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.
*
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
}
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()")
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
}
// 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 ⇒
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) ⇒
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
}
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)
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) ⇒
)
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,
unitPrice
)
-// clog.debug("execAlgorithm = %s", execAlgorithm)
+ // clog.debug("execAlgorithm = %s", execAlgorithm)
clog.debugMap("valueMap", valueMap, 1)
// This is it
* 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)
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)
* 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 {
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.
* 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
* 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
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)
}
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,
}
}
- 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,
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
)
}
}
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 = {
doFullMonthlyBilling(
userStateBootstrap,
billingMonthInfo,
+ billingTimeMillis,
defaultResourcesMap,
- calculationReason,
+ MonthlyBillingCalculation(calculationReason, billingMonthInfo),
Some(clog))
}
// 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
// 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 ⇒
//+ Utility methods
protected def rcDebugInfo(rcEvent: ResourceEventModel) = {
- rcEvent.toDebugString(false)
+ rcEvent.toDebugString
}
//- Utility methods
val alltimeAgreements = _workingUserState.agreementHistory.agreementNamesByTimeslot
// clog.debug("Computing full chargeslots")
- val (referenceTimeslot, fullChargeslots) = accounting.computeFullChargeslots(
+ val (referenceTimeslot, fullChargeslots) = timeslotComputations.computeFullChargeslots(
previousResourceEventOpt,
currentResourceEvent,
oldCredits,
_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
val previousBillingMonthUserState = findUserStateAtEndOfBillingMonth(
userStateBootstrap,
billingMonthInfo.previousMonth,
+ billingTimeMillis,
defaultResourcesMap,
calculationReason.forBillingMonthInfo(billingMonthInfo.previousMonth),
clogSome
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)
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
)
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
}
LatestResourceEventsWorker(map)
}
+
+ def findTheLatest: Option[ResourceEventModel] = {
+ resourceEvents.sortWith {
+ case (ev1, ev2) ⇒ ev1.occurredMillis <= ev2.occurredMillis
+ }.headOption
+ }
+
+ def findTheLatestID = {
+ findTheLatest.map(_.id)
+ }
}
object LatestResourceEventsSnapshot {
/**
* When the user state is initially set up.
*/
- def apply() = {
+ def apply(parentReason: Option[UserStateChangeReason]) = {
UserStateChangeReason(
- None,
+ parentReason,
None,
Map(
UserStateChangeReason.Names.`type` -> `type`,
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.
* 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
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
!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(
acc ++ Map(Timeslot(p.validFrom, p.validTo) -> parse(p.policyYAML))
}
}
+
+ Policy.policy
}
\ No newline at end of file
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]
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
}
trait MongoDBEventModel extends ExternalEventModel {
def _id: String
- override def storeID: Option[String] = Option(_id)
+ override def idInStore: Option[String] = Option(_id)
}
*/
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
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}
/**
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
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)
clog.begin("Events by OccurredMillis")
clog.withIndent {
for(event <- UserCKKL.myResourceEventsByOccurredDate) {
- clog.debug(event.toDebugString())
+ clog.debug(event.toDebugString)
}
}
clog.end("Events by OccurredMillis")
}
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)
showResourceEvents(clog)
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
showUserState(clog, userState)
showResourceEvents(clog)
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
showUserState(clog, userState)
showResourceEvents(clog)
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
showUserState(clog, userState)
clog.debugMap("DefaultResourcesMap", DefaultResourcesMap.map, 1)
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, false, TimeHelpers.nowMillis())
showUserState(clog, userState)