this._userStateBootstrap,
aquarium.currentResourceTypesMap,
InitialUserActorSetup(),
- aquarium.userStateStore.insertUserState,
- None
+ aquarium.userStateStore.insertUserState
)
// Final touch: Update agreement history in the working user state.
this._userStateBootstrap,
currentResourcesMap,
chargingReason,
- stdUserStateStoreFunc,
- None
+ stdUserStateStoreFunc
)
updateLatestResourceEventIDFrom(rcEvent)
this._workingUserState,
chargingReason,
nowBillingMonthInfo,
- None,
true
)
package gr.grnet.aquarium.charging
-import scala.collection.immutable
+import gr.grnet.aquarium.policy.{ResourceType, EffectivePriceTable, FullPriceTable}
import scala.collection.mutable
-
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
-import gr.grnet.aquarium.{Aquarium, AquariumInternalError, AquariumException}
-import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
-import com.ckkloverdos.key.{TypedKey, TypedKeySkeleton}
-import gr.grnet.aquarium.util._
-import gr.grnet.aquarium.util.date.TimeHelpers
-import gr.grnet.aquarium.charging.wallet.WalletEntry
-import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
-import gr.grnet.aquarium.charging.state.AgreementHistory
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.store.PolicyStore
-import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
+import gr.grnet.aquarium.Aquarium
+import gr.grnet.aquarium.computation.BillingMonthInfo
+import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.charging.wallet.WalletEntry
+import com.ckkloverdos.key.TypedKeySkeleton
/**
* A charging behavior indicates how charging for a resource will be done
- * wrt the various states a resource can be.
+ * wrt the various states a resource instance can be.
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-abstract class ChargingBehavior(
- final val alias: String,
- final val inputs: Set[ChargingInput],
- final val selectorHierarchy: List[List[String]] = Nil) extends Loggable {
-
- final val inputNames = inputs.map(_.name)
-
- @inline private[this] def hrs(millis: Double) = {
- val hours = millis / 1000 / 60 / 60
- val roundedHours = hours
- roundedHours
- }
-
- protected def computeCreditsToSubtract(
- oldCredits: Double,
- oldAccumulatingAmount: Double,
- newAccumulatingAmount: Double,
- timeDeltaMillis: Long,
- previousValue: Double,
- currentValue: Double,
- unitPrice: Double,
- details: Map[String, String]
- ): (Double, String) = {
- alias match {
- case ChargingBehaviorAliases.continuous ⇒
- val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
- val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
- hrs(timeDeltaMillis),
- oldAccumulatingAmount,
- unitPrice
- )
-
- (credits, explanation)
-
- case ChargingBehaviorAliases.discrete ⇒
- val credits = currentValue * unitPrice
- val explanation = "Value(%s) * Unit(%s)".format(currentValue, unitPrice)
-
- (credits, explanation)
-
- case ChargingBehaviorAliases.vmtime ⇒
- val credits = hrs(timeDeltaMillis) * unitPrice
- val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
-
- (credits, explanation)
-
- case ChargingBehaviorAliases.once ⇒
- val credits = currentValue
- val explanation = "Value(%s)".format(currentValue)
+trait ChargingBehavior {
+ def alias: String
- (credits, explanation)
-
- case name ⇒
- throw new AquariumInternalError("Cannot compute credit diff for charging behavior %s".format(name))
- }
- }
-
- protected def rcDebugInfo(rcEvent: ResourceEventModel) = {
- rcEvent.toDebugString
- }
-
- protected def computeSelectorPath(
- chargingData: mutable.Map[String, Any],
- currentResourceEvent: ResourceEventModel,
- referenceTimeslot: Timeslot,
- previousValue: Double,
- totalCredits: Double,
- oldAccumulatingAmount: Double,
- newAccumulatingAmount: Double
- ): List[String]
-
- /**
- *
- * @param chargingData
- * @param previousResourceEventOpt
- * @param currentResourceEvent
- * @param billingMonthInfo
- * @param referenceTimeslot
- * @param resourceType
- * @param agreementByTimeslot
- * @param previousValue
- * @param totalCredits
- * @param policyStore
- * @param walletEntryRecorder
- * @return The number of wallet entries recorded and the new total credits
- */
- protected def computeChargeslots(
- chargingData: mutable.Map[String, Any],
- previousResourceEventOpt: Option[ResourceEventModel],
- currentResourceEvent: ResourceEventModel,
- billingMonthInfo: BillingMonthInfo,
- referenceTimeslot: Timeslot,
- resourceType: ResourceType,
- agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
- previousValue: Double,
- totalCredits: Double,
- policyStore: PolicyStore,
- walletEntryRecorder: WalletEntry ⇒ Unit
- ): (Int, Double) = {
-
- val currentValue = currentResourceEvent.value
- val userID = currentResourceEvent.userID
- val currentDetails = currentResourceEvent.details
-
- var _oldAccumulatingAmount = getChargingData(
- chargingData,
- EnvKeys.ResourceInstanceAccumulatingAmount
- ).getOrElse(getResourceInstanceInitialAmount)
-
- var _oldTotalCredits = totalCredits
-
- var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails)
- setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount)
-
- val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(
- referenceTimeslot.from.getTime,
- referenceTimeslot.to.getTime
- )
-
- val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ {
- this.selectEffectivePriceTable(
- fullPriceTable,
- chargingData,
- currentResourceEvent,
- referenceTimeslot,
- previousValue,
- totalCredits,
- _oldAccumulatingAmount,
- _newAccumulatingAmount
- )
- }
-
- val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
- referenceTimeslot,
- resourceType,
- policyByTimeslot,
- agreementByTimeslot,
- effectivePriceTableSelector
- )
-
- val fullChargeslots = initialChargeslots.map {
- case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _, _) ⇒
- val timeDeltaMillis = stopMillis - startMillis
-
- val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
- _oldTotalCredits, // FIXME ??? Should recalculate ???
- _oldAccumulatingAmount, // FIXME ??? Should recalculate ???
- _newAccumulatingAmount, // FIXME ??? Should recalculate ???
- timeDeltaMillis,
- previousValue,
- currentValue,
- unitPrice,
- currentDetails
- )
-
- val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation)
- newChargeslot
- }
-
- if(fullChargeslots.length == 0) {
- throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
- }
-
- val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum
- val newTotalCredits = _oldTotalCredits - sumOfCreditsToSubtract
-
- val newWalletEntry = WalletEntry(
- userID,
- sumOfCreditsToSubtract,
- _oldTotalCredits,
- newTotalCredits,
- TimeHelpers.nowMillis(),
- referenceTimeslot,
- billingMonthInfo.year,
- billingMonthInfo.month,
- fullChargeslots,
- previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)),
- resourceType,
- currentResourceEvent.isSynthetic
- )
-
- logger.debug("newWalletEntry = {}", newWalletEntry.toJsonString)
-
- walletEntryRecorder.apply(newWalletEntry)
-
- (1, newTotalCredits)
- }
-
- protected def removeChargingData[T: Manifest](
- chargingData: mutable.Map[String, Any],
- envKey: TypedKey[T]
- ) = {
-
- chargingData.remove(envKey.name).asInstanceOf[Option[T]]
- }
-
- protected def getChargingData[T: Manifest](
- chargingData: mutable.Map[String, Any],
- envKey: TypedKey[T]
- ) = {
-
- chargingData.get(envKey.name).asInstanceOf[Option[T]]
- }
-
- protected def setChargingData[T: Manifest](
- chargingData: mutable.Map[String, Any],
- envKey: TypedKey[T],
- value: T
- ) = {
-
- chargingData(envKey.name) = value
- }
+ def inputs: Set[ChargingInput]
def selectEffectivePriceTable(
fullPriceTable: FullPriceTable,
totalCredits: Double,
oldAccumulatingAmount: Double,
newAccumulatingAmount: Double
- ): EffectivePriceTable = {
+ ): EffectivePriceTable
- val selectorPath = computeSelectorPath(
- chargingData,
- currentResourceEvent,
- referenceTimeslot,
- previousValue,
- totalCredits,
- oldAccumulatingAmount,
- newAccumulatingAmount
- )
-
- fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
- }
/**
*
- * @param aquarium
- * @param currentResourceEvent
- * @param resourceType
- * @param billingMonthInfo
- * @param previousResourceEventOpt
- * @param userAgreements
- * @param chargingData
- * @param totalCredits
- * @param walletEntryRecorder
- * @param clogOpt
* @return The number of wallet entries recorded and the new total credits
*/
def chargeResourceEvent(
userAgreements: AgreementHistory,
chargingData: mutable.Map[String, Any],
totalCredits: Double,
- walletEntryRecorder: WalletEntry ⇒ Unit,
- clogOpt: Option[ContextualLogger] = None
- ): (Int, Double) = {
-
- val clog = ContextualLogger.fromOther(clogOpt, logger, "chargeResourceEvent(%s)", currentResourceEvent.id)
- val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
-
- val isBillable = this.isBillableEvent(currentResourceEvent)
- val retval = if(!isBillable) {
- // The resource event is not billable.
- clog.debug("Ignoring not billable %s", currentResourceEventDebugInfo)
- (0, totalCredits)
- } else {
- // The resource event is billable.
- // Find the previous event if needed.
- // This is (potentially) needed to calculate new credit amount and new resource instance amount
- if(this.needsPreviousEventForCreditAndAmountCalculation) {
- if(previousResourceEventOpt.isDefined) {
- val previousResourceEvent = previousResourceEventOpt.get
- val previousValue = previousResourceEvent.value
-
- clog.debug("I have previous event %s", previousResourceEvent.toDebugString)
-
- computeChargeslots(
- chargingData,
- previousResourceEventOpt,
- currentResourceEvent,
- billingMonthInfo,
- Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
- resourceType,
- userAgreements.agreementByTimeslot,
- previousValue,
- totalCredits,
- aquarium.policyStore,
- walletEntryRecorder
- )
- } else {
- // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
- // Let's see if we can create a dummy previous event.
- val actualFirstEvent = currentResourceEvent
-
- // FIXME: Why && ?
- if(this.isBillableFirstEvent(actualFirstEvent) && this.mustGenerateDummyFirstEvent) {
- clog.debug("First event of its kind %s", currentResourceEventDebugInfo)
-
- val dummyFirst = this.constructDummyFirstEventFor(currentResourceEvent, billingMonthInfo.monthStartMillis)
- clog.debug("Dummy first event %s", dummyFirst.toDebugString)
-
- val previousResourceEvent = dummyFirst
- val previousValue = previousResourceEvent.value
-
- computeChargeslots(
- chargingData,
- Some(previousResourceEvent),
- currentResourceEvent,
- billingMonthInfo,
- Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
- resourceType,
- userAgreements.agreementByTimeslot,
- previousValue,
- totalCredits,
- aquarium.policyStore,
- walletEntryRecorder
- )
- } else {
- clog.debug("Ignoring first event of its kind %s", currentResourceEventDebugInfo)
- // userStateWorker.updateIgnored(currentResourceEvent)
- (0, totalCredits)
- }
- }
- } else {
- // No need for previous event. One event does it all.
- computeChargeslots(
- chargingData,
- None,
- currentResourceEvent,
- billingMonthInfo,
- Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
- resourceType,
- userAgreements.agreementByTimeslot,
- this.getResourceInstanceUndefinedAmount,
- totalCredits,
- aquarium.policyStore,
- walletEntryRecorder
- )
- }
- }
-
- retval
- }
-
- /**
- * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]]
- * and the value the respective value. This map will be used to do the actual credit charge calculation
- * by the respective algorithm.
- *
- * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context
- * has been validated before the call to `makeValueMap` is made.
- *
- * @param totalCredits the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]]
- * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]]
- * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]]
- * @param timeDelta the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]]
- * @param previousValue the value for [[gr.grnet.aquarium.charging.PreviousValueInput]]
- * @param currentValue the value for [[gr.grnet.aquarium.charging.CurrentValueInput]]
- * @param unitPrice the value for [[gr.grnet.aquarium.charging.UnitPriceInput]]
- *
- * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values.
- */
- def makeValueMap(
- totalCredits: Double,
- oldTotalAmount: Double,
- newTotalAmount: Double,
- timeDelta: Double,
- previousValue: Double,
- currentValue: Double,
- unitPrice: Double
- ): Map[ChargingInput, Any] = {
-
- ChargingBehavior.makeValueMapFor(
- this,
- totalCredits,
- oldTotalAmount,
- newTotalAmount,
- timeDelta,
- previousValue,
- currentValue,
- unitPrice)
- }
-
- def needsPreviousEventForCreditAndAmountCalculation: Boolean = {
- // If we need any variable that is related to the previous event
- // then we do need a previous event
- inputs.exists(_.isDirectlyRelatedToPreviousEvent)
- }
-
- /**
- * Given the old amount of a resource instance, the value arriving in a new resource event and the new details,
- * compute the new instance amount.
- *
- * Note that the `oldAmount` does not make sense for all types of [[gr.grnet.aquarium.charging.ChargingBehavior]],
- * in which case it is ignored.
- *
- * @param oldAccumulatingAmount the old accumulating amount
- * @param newEventValue the value contained in a newly arrived
- * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
- * @param newDetails the `details` of the newly arrived
- * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
- * @return
- */
- def computeNewAccumulatingAmount(
- oldAccumulatingAmount: Double,
- newEventValue: Double,
- newDetails: Map[String, String]
- ): Double
-
- /**
- * The initial amount.
- */
- def getResourceInstanceInitialAmount: Double
-
- /**
- * The amount used when no amount is meant to be relevant.
- *
- * For example, when there is no need for a previous event but an API requires the amount of the previous event.
- *
- * Normally, this value will never be used by client code (= charge computation code).
- */
- def getResourceInstanceUndefinedAmount: Double = Double.NaN
-
- /**
- * An event carries enough info to characterize it as billable or not.
- *
- * Typically all events are billable by default and indeed this is the default implementation
- * provided here.
- *
- * The only exception to the rule is ON events for [[gr.grnet.aquarium.charging.VMChargingBehavior]].
- */
- def isBillableEvent(event: ResourceEventModel): Boolean = true
-
- /**
- * This is called when we have the very first event for a particular resource instance, and we want to know
- * if it is billable or not.
- */
- def isBillableFirstEvent(event: ResourceEventModel): Boolean
-
- def mustGenerateDummyFirstEvent: Boolean
-
- def dummyFirstEventValue: Double = 0.0 // FIXME read from configuration
-
- def constructDummyFirstEventFor(actualFirst: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel = {
- if(!mustGenerateDummyFirstEvent) {
- throw new AquariumInternalError("constructDummyFirstEventFor() Not compliant with %s", this)
- }
-
- val newDetails = Map(
- ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
- ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
- ResourceEventModel.Names.details_aquarium_reference_event_id -> actualFirst.id,
- ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty
- )
-
- actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, newOccurredMillis)
- }
+ walletEntryRecorder: WalletEntry ⇒ Unit
+ ): (Int, Double)
- /**
- * There are resources (cost policies) for which implicit events must be generated at the end of the billing period
- * and also at the beginning of the next one. For these cases, this method must return `true`.
- *
- * The motivating example comes from the [[gr.grnet.aquarium.charging.VMChargingBehavior]] for which we
- * must implicitly assume `OFF` events at the end of the billing period and `ON` events at the beginning of the next
- * one.
- *
- */
def supportsImplicitEvents: Boolean
def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean
- @throws(classOf[Exception])
- def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccuredMillis: Long): ResourceEventModel
}
object ChargingBehavior {
currentValue: Double,
unitPrice: Double
): Map[ChargingInput, Any] = {
-
+
val inputs = chargingBehavior.inputs
var map = Map[ChargingInput, Any]()
object ChargingBehaviorAliases {
final val vmtime = "vmtime"
- final val discrete = "discrete"
final val continuous = "continuous"
final val once = "once"
}
--- /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.charging
+
+import scala.collection.immutable
+import scala.collection.mutable
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
+import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
+import com.ckkloverdos.key.TypedKey
+import gr.grnet.aquarium.util._
+import gr.grnet.aquarium.util.LogHelpers.Debug
+import gr.grnet.aquarium.util.date.TimeHelpers
+import gr.grnet.aquarium.charging.wallet.WalletEntry
+import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
+import gr.grnet.aquarium.charging.state.AgreementHistory
+import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
+import gr.grnet.aquarium.store.PolicyStore
+import gr.grnet.aquarium.charging.ChargingBehavior.EnvKeys
+
+/**
+ * A charging behavior indicates how charging for a resource will be done
+ * wrt the various states a resource can be.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+abstract class ChargingBehaviorSkeleton(
+ final val alias: String,
+ final val inputs: Set[ChargingInput],
+ final val selectorHierarchy: List[List[String]] = Nil
+) extends ChargingBehavior with Loggable {
+
+ final val inputNames = inputs.map(_.name)
+
+ @inline private[this] def hrs(millis: Double) = {
+ val hours = millis / 1000 / 60 / 60
+ val roundedHours = hours
+ roundedHours
+ }
+
+ protected def computeCreditsToSubtract(
+ oldCredits: Double,
+ oldAccumulatingAmount: Double,
+ newAccumulatingAmount: Double,
+ timeDeltaMillis: Long,
+ previousValue: Double,
+ currentValue: Double,
+ unitPrice: Double,
+ details: Map[String, String]
+ ): (Double, String) = {
+ alias match {
+ case ChargingBehaviorAliases.continuous ⇒
+ val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
+ val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
+ hrs(timeDeltaMillis),
+ oldAccumulatingAmount,
+ unitPrice
+ )
+
+ (credits, explanation)
+
+ case ChargingBehaviorAliases.vmtime ⇒
+ val credits = hrs(timeDeltaMillis) * unitPrice
+ val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
+
+ (credits, explanation)
+
+ case ChargingBehaviorAliases.once ⇒
+ val credits = currentValue
+ val explanation = "Value(%s)".format(currentValue)
+
+ (credits, explanation)
+
+ case name ⇒
+ throw new AquariumInternalError("Cannot compute credit diff for charging behavior %s".format(name))
+ }
+ }
+
+ protected def rcDebugInfo(rcEvent: ResourceEventModel) = {
+ rcEvent.toDebugString
+ }
+
+ protected def computeSelectorPath(
+ chargingData: mutable.Map[String, Any],
+ currentResourceEvent: ResourceEventModel,
+ referenceTimeslot: Timeslot,
+ previousValue: Double,
+ totalCredits: Double,
+ oldAccumulatingAmount: Double,
+ newAccumulatingAmount: Double
+ ): List[String]
+
+ /**
+ *
+ * @param chargingData
+ * @param previousResourceEventOpt
+ * @param currentResourceEvent
+ * @param billingMonthInfo
+ * @param referenceTimeslot
+ * @param resourceType
+ * @param agreementByTimeslot
+ * @param previousValue
+ * @param totalCredits
+ * @param policyStore
+ * @param walletEntryRecorder
+ * @return The number of wallet entries recorded and the new total credits
+ */
+ protected def computeChargeslots(
+ chargingData: mutable.Map[String, Any],
+ previousResourceEventOpt: Option[ResourceEventModel],
+ currentResourceEvent: ResourceEventModel,
+ billingMonthInfo: BillingMonthInfo,
+ referenceTimeslot: Timeslot,
+ resourceType: ResourceType,
+ agreementByTimeslot: immutable.SortedMap[Timeslot, UserAgreementModel],
+ previousValue: Double,
+ totalCredits: Double,
+ policyStore: PolicyStore,
+ walletEntryRecorder: WalletEntry ⇒ Unit
+ ): (Int, Double) = {
+
+ val currentValue = currentResourceEvent.value
+ val userID = currentResourceEvent.userID
+ val currentDetails = currentResourceEvent.details
+
+ var _oldAccumulatingAmount = getChargingData(
+ chargingData,
+ EnvKeys.ResourceInstanceAccumulatingAmount
+ ).getOrElse(getResourceInstanceInitialAmount)
+
+ var _oldTotalCredits = totalCredits
+
+ var _newAccumulatingAmount = this.computeNewAccumulatingAmount(_oldAccumulatingAmount, currentValue, currentDetails)
+ setChargingData(chargingData, EnvKeys.ResourceInstanceAccumulatingAmount, _newAccumulatingAmount)
+
+ val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(
+ referenceTimeslot.from.getTime,
+ referenceTimeslot.to.getTime
+ )
+
+ val effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable = fullPriceTable ⇒ {
+ this.selectEffectivePriceTable(
+ fullPriceTable,
+ chargingData,
+ currentResourceEvent,
+ referenceTimeslot,
+ previousValue,
+ totalCredits,
+ _oldAccumulatingAmount,
+ _newAccumulatingAmount
+ )
+ }
+
+ val initialChargeslots = TimeslotComputations.computeInitialChargeslots(
+ referenceTimeslot,
+ resourceType,
+ policyByTimeslot,
+ agreementByTimeslot,
+ effectivePriceTableSelector
+ )
+
+ val fullChargeslots = initialChargeslots.map {
+ case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _, _) ⇒
+ val timeDeltaMillis = stopMillis - startMillis
+
+ val (creditsToSubtract, explanation) = this.computeCreditsToSubtract(
+ _oldTotalCredits, // FIXME ??? Should recalculate ???
+ _oldAccumulatingAmount, // FIXME ??? Should recalculate ???
+ _newAccumulatingAmount, // FIXME ??? Should recalculate ???
+ timeDeltaMillis,
+ previousValue,
+ currentValue,
+ unitPrice,
+ currentDetails
+ )
+
+ val newChargeslot = chargeslot.copyWithCreditsToSubtract(creditsToSubtract, explanation)
+ newChargeslot
+ }
+
+ if(fullChargeslots.length == 0) {
+ throw new AquariumInternalError("No chargeslots computed for resource event %s".format(currentResourceEvent.id))
+ }
+
+ val sumOfCreditsToSubtract = fullChargeslots.map(_.creditsToSubtract).sum
+ val newTotalCredits = _oldTotalCredits - sumOfCreditsToSubtract
+
+ val newWalletEntry = WalletEntry(
+ userID,
+ sumOfCreditsToSubtract,
+ _oldTotalCredits,
+ newTotalCredits,
+ TimeHelpers.nowMillis(),
+ referenceTimeslot,
+ billingMonthInfo.year,
+ billingMonthInfo.month,
+ fullChargeslots,
+ previousResourceEventOpt.map(List(_, currentResourceEvent)).getOrElse(List(currentResourceEvent)),
+ resourceType,
+ currentResourceEvent.isSynthetic
+ )
+
+ logger.debug("newWalletEntry = {}", newWalletEntry.toJsonString)
+
+ walletEntryRecorder.apply(newWalletEntry)
+
+ (1, newTotalCredits)
+ }
+
+ protected def removeChargingData[T: Manifest](
+ chargingData: mutable.Map[String, Any],
+ envKey: TypedKey[T]
+ ) = {
+
+ chargingData.remove(envKey.name).asInstanceOf[Option[T]]
+ }
+
+ protected def getChargingData[T: Manifest](
+ chargingData: mutable.Map[String, Any],
+ envKey: TypedKey[T]
+ ) = {
+
+ chargingData.get(envKey.name).asInstanceOf[Option[T]]
+ }
+
+ protected def setChargingData[T: Manifest](
+ chargingData: mutable.Map[String, Any],
+ envKey: TypedKey[T],
+ value: T
+ ) = {
+
+ chargingData(envKey.name) = value
+ }
+
+ def selectEffectivePriceTable(
+ fullPriceTable: FullPriceTable,
+ chargingData: mutable.Map[String, Any],
+ currentResourceEvent: ResourceEventModel,
+ referenceTimeslot: Timeslot,
+ previousValue: Double,
+ totalCredits: Double,
+ oldAccumulatingAmount: Double,
+ newAccumulatingAmount: Double
+ ): EffectivePriceTable = {
+
+ val selectorPath = computeSelectorPath(
+ chargingData,
+ currentResourceEvent,
+ referenceTimeslot,
+ previousValue,
+ totalCredits,
+ oldAccumulatingAmount,
+ newAccumulatingAmount
+ )
+
+ fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
+ }
+
+ /**
+ * A generic implementation for charging a resource event.
+ * TODO: Ditch this in favor of completely ahdoc behaviors.
+ *
+ * @param aquarium
+ * @param currentResourceEvent
+ * @param resourceType
+ * @param billingMonthInfo
+ * @param previousResourceEventOpt
+ * @param userAgreements
+ * @param chargingData
+ * @param totalCredits
+ * @param walletEntryRecorder
+ * @return The number of wallet entries recorded and the new total credits
+ */
+ def chargeResourceEvent(
+ aquarium: Aquarium,
+ currentResourceEvent: ResourceEventModel,
+ resourceType: ResourceType,
+ billingMonthInfo: BillingMonthInfo,
+ previousResourceEventOpt: Option[ResourceEventModel],
+ userAgreements: AgreementHistory,
+ chargingData: mutable.Map[String, Any],
+ totalCredits: Double,
+ walletEntryRecorder: WalletEntry ⇒ Unit
+ ): (Int, Double) = {
+
+ val currentResourceEventDebugInfo = rcDebugInfo(currentResourceEvent)
+
+ val isBillable = this.isBillableEvent(currentResourceEvent)
+ val retval = if(!isBillable) {
+ // The resource event is not billable.
+ Debug(logger, "Ignoring not billable %s", currentResourceEventDebugInfo)
+ (0, totalCredits)
+ } else {
+ // The resource event is billable.
+ // Find the previous event if needed.
+ // This is (potentially) needed to calculate new credit amount and new resource instance amount
+ if(this.needsPreviousEventForCreditAndAmountCalculation) {
+ if(previousResourceEventOpt.isDefined) {
+ val previousResourceEvent = previousResourceEventOpt.get
+ val previousValue = previousResourceEvent.value
+
+ Debug(logger, "I have previous event %s", previousResourceEvent.toDebugString)
+
+ computeChargeslots(
+ chargingData,
+ previousResourceEventOpt,
+ currentResourceEvent,
+ billingMonthInfo,
+ Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
+ resourceType,
+ userAgreements.agreementByTimeslot,
+ previousValue,
+ totalCredits,
+ aquarium.policyStore,
+ walletEntryRecorder
+ )
+ } else {
+ // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
+ // Let's see if we can create a dummy previous event.
+ val actualFirstEvent = currentResourceEvent
+
+ // FIXME: Why && ?
+ if(this.isBillableFirstEvent(actualFirstEvent) && this.mustGenerateDummyFirstEvent) {
+ Debug(logger, "First event of its kind %s", currentResourceEventDebugInfo)
+
+ val dummyFirst = this.constructDummyFirstEventFor(currentResourceEvent, billingMonthInfo.monthStartMillis)
+ Debug(logger, "Dummy first event %s", dummyFirst.toDebugString)
+
+ val previousResourceEvent = dummyFirst
+ val previousValue = previousResourceEvent.value
+
+ computeChargeslots(
+ chargingData,
+ Some(previousResourceEvent),
+ currentResourceEvent,
+ billingMonthInfo,
+ Timeslot(previousResourceEvent.occurredMillis, currentResourceEvent.occurredMillis),
+ resourceType,
+ userAgreements.agreementByTimeslot,
+ previousValue,
+ totalCredits,
+ aquarium.policyStore,
+ walletEntryRecorder
+ )
+ } else {
+ Debug(logger, "Ignoring first event of its kind %s", currentResourceEventDebugInfo)
+ // userStateWorker.updateIgnored(currentResourceEvent)
+ (0, totalCredits)
+ }
+ }
+ } else {
+ // No need for previous event. One event does it all.
+ computeChargeslots(
+ chargingData,
+ None,
+ currentResourceEvent,
+ billingMonthInfo,
+ Timeslot(currentResourceEvent.occurredMillis, currentResourceEvent.occurredMillis + 1),
+ resourceType,
+ userAgreements.agreementByTimeslot,
+ this.getResourceInstanceUndefinedAmount,
+ totalCredits,
+ aquarium.policyStore,
+ walletEntryRecorder
+ )
+ }
+ }
+
+ retval
+ }
+
+ /**
+ * Generate a map where the key is a [[gr.grnet.aquarium.charging.ChargingInput]]
+ * and the value the respective value. This map will be used to do the actual credit charge calculation
+ * by the respective algorithm.
+ *
+ * Values are obtained from a corresponding context, which is provided by the parameters. We assume that this context
+ * has been validated before the call to `makeValueMap` is made.
+ *
+ * @param totalCredits the value for [[gr.grnet.aquarium.charging.TotalCreditsInput.]]
+ * @param oldTotalAmount the value for [[gr.grnet.aquarium.charging.OldTotalAmountInput]]
+ * @param newTotalAmount the value for [[gr.grnet.aquarium.charging.NewTotalAmountInput]]
+ * @param timeDelta the value for [[gr.grnet.aquarium.charging.TimeDeltaInput]]
+ * @param previousValue the value for [[gr.grnet.aquarium.charging.PreviousValueInput]]
+ * @param currentValue the value for [[gr.grnet.aquarium.charging.CurrentValueInput]]
+ * @param unitPrice the value for [[gr.grnet.aquarium.charging.UnitPriceInput]]
+ *
+ * @return a map from [[gr.grnet.aquarium.charging.ChargingInput]]s to respective values.
+ */
+ def makeValueMap(
+ totalCredits: Double,
+ oldTotalAmount: Double,
+ newTotalAmount: Double,
+ timeDelta: Double,
+ previousValue: Double,
+ currentValue: Double,
+ unitPrice: Double
+ ): Map[ChargingInput, Any] = {
+
+ ChargingBehavior.makeValueMapFor(
+ this,
+ totalCredits,
+ oldTotalAmount,
+ newTotalAmount,
+ timeDelta,
+ previousValue,
+ currentValue,
+ unitPrice)
+ }
+
+ def needsPreviousEventForCreditAndAmountCalculation: Boolean = {
+ // If we need any variable that is related to the previous event
+ // then we do need a previous event
+ inputs.exists(_.isDirectlyRelatedToPreviousEvent)
+ }
+
+ /**
+ * Given the old amount of a resource instance, the value arriving in a new resource event and the new details,
+ * compute the new instance amount.
+ *
+ * Note that the `oldAmount` does not make sense for all types of [[gr.grnet.aquarium.charging.ChargingBehavior]],
+ * in which case it is ignored.
+ *
+ * @param oldAccumulatingAmount the old accumulating amount
+ * @param newEventValue the value contained in a newly arrived
+ * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
+ * @param newDetails the `details` of the newly arrived
+ * [[gr.grnet.aquarium.event.model.resource.ResourceEventModel]]
+ * @return
+ */
+ def computeNewAccumulatingAmount(
+ oldAccumulatingAmount: Double,
+ newEventValue: Double,
+ newDetails: Map[String, String]
+ ): Double
+
+ /**
+ * The initial amount.
+ */
+ def getResourceInstanceInitialAmount: Double
+
+ /**
+ * The amount used when no amount is meant to be relevant.
+ *
+ * For example, when there is no need for a previous event but an API requires the amount of the previous event.
+ *
+ * Normally, this value will never be used by client code (= charge computation code).
+ */
+ def getResourceInstanceUndefinedAmount: Double = Double.NaN
+
+ /**
+ * An event carries enough info to characterize it as billable or not.
+ *
+ * Typically all events are billable by default and indeed this is the default implementation
+ * provided here.
+ *
+ * The only exception to the rule is ON events for [[gr.grnet.aquarium.charging.VMChargingBehavior]].
+ */
+ def isBillableEvent(event: ResourceEventModel): Boolean = true
+
+ /**
+ * This is called when we have the very first event for a particular resource instance, and we want to know
+ * if it is billable or not.
+ */
+ def isBillableFirstEvent(event: ResourceEventModel): Boolean
+
+ def mustGenerateDummyFirstEvent: Boolean
+
+ def dummyFirstEventValue: Double = 0.0 // FIXME read from configuration
+
+ def constructDummyFirstEventFor(actualFirst: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel = {
+ if(!mustGenerateDummyFirstEvent) {
+ throw new AquariumInternalError("constructDummyFirstEventFor() Not compliant with %s", this)
+ }
+
+ val newDetails = Map(
+ ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
+ ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
+ ResourceEventModel.Names.details_aquarium_reference_event_id -> actualFirst.id,
+ ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> actualFirst.stringIDInStoreOrEmpty
+ )
+
+ actualFirst.withDetailsAndValue(newDetails, dummyFirstEventValue, newOccurredMillis)
+ }
+
+ /**
+ * There are resources (cost policies) for which implicit events must be generated at the end of the billing period
+ * and also at the beginning of the next one. For these cases, this method must return `true`.
+ *
+ * The motivating example comes from the [[gr.grnet.aquarium.charging.VMChargingBehavior]] for which we
+ * must implicitly assume `OFF` events at the end of the billing period and `ON` events at the beginning of the next
+ * one.
+ *
+ */
+ def supportsImplicitEvents: Boolean
+
+ def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel): Boolean
+
+ @throws(classOf[Exception])
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long): ResourceEventModel
+}
import gr.grnet.aquarium.computation.BillingMonthInfo
import gr.grnet.aquarium.charging.state.UserStateBootstrap
import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.util.{Lifecycle, Loggable, ContextualLogger}
+import gr.grnet.aquarium.util.{Lifecycle, Loggable}
+import gr.grnet.aquarium.util.LogHelpers.Debug
+import gr.grnet.aquarium.util.LogHelpers.Warn
+import gr.grnet.aquarium.util.LogHelpers.DebugSeq
import gr.grnet.aquarium.util.date.{MutableDateCalc, TimeHelpers}
import gr.grnet.aquarium.{AquariumInternalError, AquariumAwareSkeleton}
import gr.grnet.aquarium.charging.state.{WorkingUserState, UserStateModel, StdUserState}
userStateBootstrap: UserStateBootstrap,
defaultResourceTypesMap: Map[String, ResourceType],
chargingReason: ChargingReason,
- userStateRecorder: UserStateModel ⇒ UserStateModel,
- clogOpt: Option[ContextualLogger]
+ userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
- val clog = ContextualLogger.fromOther(
- clogOpt,
- logger,
- "findOrCalculateWorkingUserStateAtEndOfBillingMonth(%s)", billingMonthInfo.toShortDebugString)
- clog.begin()
-
- lazy val clogSome = Some(clog)
-
def computeFullMonthBillingAndSaveState(): WorkingUserState = {
val workingUserState = replayFullMonthBilling(
userStateBootstrap,
billingMonthInfo,
defaultResourceTypesMap,
chargingReason,
- userStateRecorder,
- clogSome
+ userStateRecorder
)
val newChargingReason = MonthlyBillChargingReason(chargingReason, billingMonthInfo)
// We always save the state when it is a full month billing
val monthlyUserState1 = userStateRecorder.apply(monthlyUserState0)
- clog.debug("Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
+ Debug(logger, "Stored full %s %s", billingMonthInfo.toDebugString, monthlyUserState1.toJsonString)
workingUserState
}
if(billingMonthStopMillis < userCreationMillis) {
// If the user did not exist for this billing month, piece of cake
- clog.debug("User did not exist before %s", userCreationDateCalc)
+ Debug(logger, "User did not exist before %s", userCreationDateCalc)
// TODO: The initial user state might have already been created.
// First ask if it exists and compute only if not
InitialUserStateSetup(Some(chargingReason)) // we record the originating calculation reason
)
- logger.debug("Created (from bootstrap) initial user state %s".format(initialUserState0))
+ Debug(logger, "Created (from bootstrap) initial user state %s", initialUserState0)
// We always save the initial state
val initialUserState1 = userStateRecorder.apply(initialUserState0)
- clog.debug("Stored initial state = %s", initialUserState1.toJsonString)
- clog.end()
+ Debug(logger, "Stored initial state = %s", initialUserState1.toJsonString)
return initialUserState1.toWorkingUserState(defaultResourceTypesMap)
}
latestUserStateOpt match {
case None ⇒
// Not found, must compute
- clog.debug("No user state found from cache, will have to (re)compute")
- val result = computeFullMonthBillingAndSaveState
- clog.end()
- result
+ Debug(logger, "No user state found from cache, will have to (re)compute")
+ computeFullMonthBillingAndSaveState
case Some(latestUserState) ⇒
// Found a "latest" user state but need to see if it is indeed the true and one latest.
case 0 ⇒
// NOTE: Keep the caller's calculation reason
val userStateModel = latestUserState.newWithChargingReason(chargingReason)
- clog.end()
userStateModel.toWorkingUserState(defaultResourceTypesMap)
// We had more, so must recompute
case n if n > 0 ⇒
- clog.debug(
+ Debug(logger,
"Found %s out of sync events (%s more), will have to (re)compute user state", actualOOSEventsCounter, n)
- val workingUserState = computeFullMonthBillingAndSaveState
- clog.end()
- workingUserState
+ computeFullMonthBillingAndSaveState
// We had less????
case n if n < 0 ⇒
val errMsg = "Found %s out of sync events (%s less). DB must be inconsistent".format(actualOOSEventsCounter, n)
- clog.warn(errMsg)
+ Warn(logger, errMsg)
throw new AquariumInternalError(errMsg)
}
}
* @param workingUserState
* @param chargingReason
* @param billingMonthInfo
- * @param clogOpt
*/
def processResourceEvent(
resourceEvent: ResourceEventModel,
workingUserState: WorkingUserState,
chargingReason: ChargingReason,
billingMonthInfo: BillingMonthInfo,
- clogOpt: Option[ContextualLogger],
- updateLatestMiilis: Boolean
+ updateLatestMillis: Boolean
): Unit = {
val resourceTypeName = resourceEvent.resource
workingUserState.workingAgreementHistory.toAgreementHistory,
workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo),
workingUserState.totalCredits,
- workingUserState.walletEntries += _,
- clogOpt
+ workingUserState.walletEntries += _
)
- if(updateLatestMiilis) {
+ if(updateLatestMillis) {
workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
}
resourceEvents: Traversable[ResourceEventModel],
workingUserState: WorkingUserState,
chargingReason: ChargingReason,
- billingMonthInfo: BillingMonthInfo,
- clogOpt: Option[ContextualLogger] = None
+ billingMonthInfo: BillingMonthInfo
): Unit = {
var _counter = 0
workingUserState,
chargingReason,
billingMonthInfo,
- clogOpt,
false
)
billingMonthInfo: BillingMonthInfo,
defaultResourceTypesMap: Map[String, ResourceType],
chargingReason: ChargingReason,
- userStateRecorder: UserStateModel ⇒ UserStateModel,
- clogOpt: Option[ContextualLogger]
+ userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
replayMonthChargingUpTo(
userStateBootstrap,
defaultResourceTypesMap,
chargingReason,
- userStateRecorder,
- clogOpt
+ userStateRecorder
)
}
* @param resourceTypesMap
* @param chargingReason
* @param userStateRecorder
- * @param clogOpt
* @return
*/
def replayMonthChargingUpTo(
userStateBootstrap: UserStateBootstrap,
resourceTypesMap: Map[String, ResourceType],
chargingReason: ChargingReason,
- userStateRecorder: UserStateModel ⇒ UserStateModel,
- clogOpt: Option[ContextualLogger]
+ userStateRecorder: UserStateModel ⇒ UserStateModel
): WorkingUserState = {
val isFullMonthBilling = billingEndTimeMillis == billingMonthInfo.monthStopMillis
val userID = userStateBootstrap.userID
- val clog = ContextualLogger.fromOther(
- clogOpt,
- logger,
- "replayMonthChargingUpTo(%s)", TimeHelpers.toYYYYMMDDHHMMSSSSS(billingEndTimeMillis))
- clog.begin()
-
- clog.debug("%s", chargingReason)
-
- val clogSome = Some(clog)
+ Debug(logger, "%s", chargingReason)
// In order to replay the full month, we start with the state at the beginning of the month.
val previousBillingMonthInfo = billingMonthInfo.previousMonth
userStateBootstrap,
resourceTypesMap,
chargingReason,
- userStateRecorder,
- clogSome
+ userStateRecorder
)
// FIXME the below comments
// 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
- clog.debug("workingUserState=%s", workingUserState)
- clog.debug("previousBillingMonthUserState(%s) = %s".format(
+ Debug(logger, "workingUserState=%s", workingUserState)
+ Debug(logger, "previousBillingMonthUserState(%s) = %s",
previousBillingMonthInfo.toShortDebugString,
- workingUserState)
+ workingUserState
)
var _rcEventsCounter = 0
billingEndTimeMillis // to requested time
) { currentResourceEvent ⇒
- clog.debug("Processing %s".format(currentResourceEvent))
+ Debug(logger, "Processing %s", currentResourceEvent)
processResourceEvent(
currentResourceEvent,
workingUserState,
chargingReason,
billingMonthInfo,
- clogSome,
false
)
workingUserState.latestUpdateMillis = TimeHelpers.nowMillis()
}
- clog.debug("Found %s resource events for month %s".format(_rcEventsCounter, billingMonthInfo.toShortDebugString))
+ Debug(logger, "Found %s resource events for month %s",
+ _rcEventsCounter,
+ billingMonthInfo.toShortDebugString
+ )
if(isFullMonthBilling) {
// For the remaining events which must contribute an implicit OFF, we collect those OFFs
)
if(generatorsOfImplicitEnds.lengthCompare(1) >= 0 || theirImplicitEnds.lengthCompare(1) >= 0) {
- clog.debug("")
- clog.debug("Process implicitly issued events")
- clog.debugSeq("generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
- clog.debugSeq("theirImplicitEnds", theirImplicitEnds, 0)
+ Debug(logger, "")
+ Debug(logger, "Process implicitly issued events")
+ DebugSeq(logger, "generatorsOfImplicitEnds", generatorsOfImplicitEnds, 0)
+ DebugSeq(logger, "theirImplicitEnds", theirImplicitEnds, 0)
}
// Now, the previous and implicitly started must be our base for the following computation, so we create an
theirImplicitEnds,
specialWorkingUserState,
chargingReason,
- billingMonthInfo,
- clogSome
+ billingMonthInfo
)
workingUserState.walletEntries ++= specialWorkingUserState.walletEntries
workingUserState.totalCredits = specialWorkingUserState.totalCredits
}
- clog.end()
workingUserState
}
}
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
final class ContinuousChargingBehavior
- extends ChargingBehavior(
+ extends ChargingBehaviorSkeleton(
ChargingBehaviorAliases.continuous,
Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
+++ /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.charging
-
-import gr.grnet.aquarium.AquariumException
-
-/**
- * Encapsulates the possible states that a resource with an
- * [[gr.grnet.aquarium.charging.VMChargingBehavior]]
- * can be.
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-sealed abstract class OnOffPolicyResourceState(val state: String) {
- def isOn: Boolean = !isOff
- def isOff: Boolean = !isOn
-}
-
-object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.on) {
- override def isOn = true
-}
-
-object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.off) {
- override def isOff = true
-}
-
-object OnOffPolicyResourceState {
- object Names {
- final val on = "on"
- final val off = "off"
- }
-}
-
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
final class OnceChargingBehavior
- extends ChargingBehavior(
+ extends ChargingBehaviorSkeleton(
ChargingBehaviorAliases.once,
Set(ChargingBehaviorNameInput, CurrentValueInput)) {
package gr.grnet.aquarium.charging
-import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
+import gr.grnet.aquarium.AquariumInternalError
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
import scala.collection.mutable
import VMChargingBehavior.Selectors.Power
-import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable}
/**
* The new [[gr.grnet.aquarium.charging.ChargingBehavior]] for VMs usage.
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
final class VMChargingBehavior
- extends ChargingBehavior(
+ extends ChargingBehaviorSkeleton(
ChargingBehaviorAliases.vmtime,
Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput),
List(List(Power.powerOn, Power.powerOff))) {
/**
* Provides information explaining the reason Aquarium calculated a new
- * [[gr.grnet.aquarium.computation.state.UserState]].
+ * [[gr.grnet.aquarium.charging.state.UserStateModel]].
*/
case class ChargingReason(
details: Map[String, Any],
package gr.grnet.aquarium.computation
import collection.immutable.SortedMap
-import com.ckkloverdos.maybe.{NoVal, Maybe}
-import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
-import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.util.Loggable
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
import gr.grnet.aquarium.policy._
import collection.immutable
-import com.ckkloverdos.maybe.Just
import gr.grnet.aquarium.policy.ResourceType
import gr.grnet.aquarium.policy.EffectiveUnitPrice
-import gr.grnet.aquarium.charging.{ChargingBehavior, Chargeslot}
+import gr.grnet.aquarium.charging.Chargeslot
/**
* Methods for converting accounting events to wallet entries.
def splitTimeslotByPoliciesAndAgreements(
referenceTimeslot: Timeslot,
policyTimeslots: List[Timeslot],
- agreementTimeslots: List[Timeslot],
- clogM: Maybe[ContextualLogger] = NoVal
+ agreementTimeslots: List[Timeslot]
): List[Timeslot] = {
// Align policy and agreement validity timeslots to the referenceTimeslot
policy: PolicyModel,
agreement: UserAgreementModel,
resourceType: ResourceType,
- effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
- clogOpt: Option[ContextualLogger] = None
+ effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
): SortedMap[Timeslot, Double] = {
- val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
-
// Note that most of the code is taken from calcChangeChunks()
val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType, effectivePriceTableSelector)
ret map {case (t,p) => (t,p.unitPrice)}
resourceType: ResourceType,
policyByTimeslot: SortedMap[Timeslot, PolicyModel],
agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
- effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable,
- clogOpt: Option[ContextualLogger] = None
+ effectivePriceTableSelector: FullPriceTable ⇒ EffectivePriceTable
): List[Chargeslot] = {
- val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
-
val policyTimeslots = policyByTimeslot.keySet
val agreementTimeslots = agreementByTimeslot.keySet
// 1. Round ONE: split time according to overlapping policies and agreements.
//val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
- val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
+ val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList)
// 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
// fine-grained timeslots according to applicable algorithms.
policy,
userAgreement,
resourceType,
- effectivePriceTableSelector,
- Some(clog)
+ effectivePriceTableSelector
)
// Now, the timeslots must be the same
object PolicyModel {
trait NamesT {
- final val a = 1
+ final val id = "id"
+ final val parentID = "parentID"
+ final val idInStore = "idInStore"
}
final object Names extends NamesT
-
- def fromJsonString(json: String): PolicyModel = {
- StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json))
- }
}
package gr.grnet.aquarium.policy
import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters}
/**
* Standard implementation of Aquarium policy model.
def idInStore = Some(id)
}
+
+object StdPolicy {
+ def fromJsonString(json: String): StdPolicy = {
+ StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json))
+ }
+}
+++ /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.util
-
-import org.slf4j.Logger
-import com.ckkloverdos.maybe.Failed
-
-/**
- * A logger that keeps track of working context and indentation level.
- *
- * This is mostly useful in single-threaded debugging sessions.
- *
- * A sample output follows:
- *
- * {{{
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() +++ [Events by OccurredMillis] +++
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() --- [Events by OccurredMillis] ---
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2012-01) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) No user state found from cache, will have to (re)compute
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-12) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) No user state found from cache, will have to (re)compute
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-11) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) BEGIN
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) User did not exist before 2011-11-01 00:00:00.000
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) Returning INITIAL state [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,0,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,InitialUserStateSetup,0,None,4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-10) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-11) calculationReason = MonthlyBillChargingReason$(2011-11)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-11) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-11) RETURN UserState(true,CKKL,1320098400000,1,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-11),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-11) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-11) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-12) calculationReason = MonthlyBillChargingReason$(2011-12)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-12) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-12) RETURN UserState(true,CKKL,1320098400000,2,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List()),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2011-12),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2011-12) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - …ndUserStateAtEndOfBillingMonth(2011-12) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) +++ [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] +++
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) Cost policy OnOffCostPolicy for DSLResource(vmtime,Hr,OnOffCostPolicy,true,instanceID)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) PreviousM None
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) Ignoring first event of its kind EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - walletEntriesForResourceEvent(0) --- [EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)] ---
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2012-01) calculationReason = MonthlyBillChargingReason$(2012-01)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2012-01) Saved [_id=4fa7e12ba0eee3db73fbe8d0] UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2012-01) RETURN UserState(true,CKKL,1320098400000,3,false,null,ImplicitlyIssuedResourceEventsSnapshot(List()),List(),List(),LatestResourceEventsSnapshot(List(StdResourceEvent(0,1325368800000,1325368800000,CKKL,synnefo,vmtime,VM.1,0.0,1.0,Map()))),0,0,IMStateSnapshot(StdIMEvent(,1320098400000,1320098400000,CKKL,,true,user,1.0,create,Map())),CreditSnapshot(0.0),AgreementHistory(List(AgreementHistoryItem(default, 2011-11-01 00:00:00.000, 292278994-08-17 07:12:55.807))),OwnedResourcesSnapshot(List()),List(),1320098400000,MonthlyBillChargingReason$(2012-01),0,Some(4fa7e12ba0eee3db73fbe8d0),4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - replayFullMonthBilling(2012-01) END
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() _id = 4fa7e12ba0eee3db73fbe8d0
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() parentId = Some(4fa7e12ba0eee3db73fbe8d0)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() credits = 0.0
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() changeReason = MonthlyBillChargingReason$(2012-01)
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() implicitlyIssued [#=0] = List()
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() latestResourceEvents [#=1]:
-DEBUG 17:50:19 g.g.a.user.UserStateComputationsTest - testOrphanOFF() EVENT(0, [2012-01-01 00:00:00], 0.0, vmtime::VM.1, Map(), CKKL, synnefo)
-DEBUG 17:50:20 g.g.a.user.UserStateComputationsTest - testOrphanOFF() newWalletEntries [#=0] = List()
- * }}}
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-final class ContextualLogger(val logger: Logger, fmt: String, args: Any*) {
- val ctx = ContextualLogger.fixCtx(fmt.format(args:_*))
-
- private[this] var _nesting = 0
-
- private def nestMsg(fmt: String, args: Any*) = {
- val msg = fmt.format(args: _*)
- _nesting match {
- case 0 ⇒
- msg
- case n ⇒
- val buffer = new java.lang.StringBuilder(n + msg.size)
- var _i = 0
- while(_i < (n * 2)) {
- buffer.append(' ')
- _i = _i + 1
- }
- buffer.append(msg)
- buffer.toString
- }
- }
-
- def isDebugEnabled = logger.isDebugEnabled
-
- def nesting = _nesting
-
- def indentAs(other: ContextualLogger): this.type = {
- _nesting = other.nesting
- this
- }
-
- def indent(): this.type = {
- _nesting = _nesting + 1
- this
- }
-
- def unindent(): this.type = {
- _nesting = _nesting - 1
- this
- }
-
- def withIndent[A](f: => A): Unit = {
- import com.ckkloverdos.maybe.effect
- this.indent()
- effect(f){}{unindent()}
- }
-
- def debug(fmt: String, args: Any*): Unit = {
- if(logger.isDebugEnabled) {
- val msg = ctx + " " + nestMsg(fmt, args:_*)
- logger.debug(msg)
- }
- }
-
- def info(fmt: String, args: Any*): Unit = {
- if(logger.isInfoEnabled) {
- val msg = ctx + " " + nestMsg(fmt, args:_*)
- logger.info(msg)
- }
- }
-
- def warn(fmt: String, args: Any*): Unit = {
- if(logger.isWarnEnabled) {
- val msg = ctx + " " + nestMsg(fmt, args:_*)
- logger.warn(msg)
- }
- }
-
- def error(fmt: String, args: Any*): Unit = {
- if(logger.isErrorEnabled) {
- val msg = ctx + " " + nestMsg(fmt, args:_*)
- logger.error(msg)
- }
- }
-
- def error(t: Throwable, fmt: String, args: Any*): Unit = {
- if(logger.isErrorEnabled) {
- val msg = ctx + " " + nestMsg(fmt, args:_*)
- logger.error(msg, t)
- }
- }
-
- def error(failed: Failed): Unit = {
- this.error(failed.exception, "")
- }
-
- def begin(message: String = ""): Unit = {
- if(message == "") debug("BEGIN") else debug("+++ [%s] +++", message)
- indent()
- }
-
- def end(message: String = ""): Unit = {
- unindent()
- if(message == "") debug("END") else debug("--- [%s] ---", message)
- }
-
- def debugMap[K, V](name: String, map: scala.collection.Map[K, V], oneLineLimit: Int = 3): Unit = {
- if(this.isDebugEnabled) {
- val mapSize = map.size
- if(mapSize <= oneLineLimit) {
- this.debug("%s [#=%s] = %s", name, mapSize, map)
- } else {
- this.debug("%s [#=%s]:", name, mapSize)
- val maxKeySize = maxStringSize(map.keySet)
- this.withIndent {
- for((k, v) <- map) {
- this.debug("%s -> %s", rpad(k.toString, maxKeySize), v)
- }
- }
- }
- }
- }
-
- def debugSeq[T](name: String, seq: scala.collection.Seq[T], oneLineLimit: Int = 3): Unit = {
- if(this.isDebugEnabled) {
- val seqSize = seq.size
- if(seqSize <= oneLineLimit) {
- this.debug("%s [#=%s] = %s", name, seqSize, seq)
- } else {
- this.debug("%s [#=%s]: ", name, seqSize)
- this.withIndent(seq.foreach(this.debug("%s", _)))
- }
- }
- }
-
- def debugSet[T](name: String, set: scala.collection.Set[T], oneLineLimit: Int = 3): Unit = {
- if(this.isDebugEnabled) {
- val setSize = set.size
- if(setSize <= oneLineLimit) {
- this.debug("%s [#=%s] = %s", name, setSize, set)
- } else {
- this.debug("%s [#=%s]: ", name, setSize)
- this.withIndent(set.foreach(this.debug("%s", _)))
- }
- }
- }
-}
-
-/**
- * Companion object of [[gr.grnet.aquarium.util.ContextualLogger]].
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-object ContextualLogger {
- final val MaxCtxLength = 40
- final val ELLIPSIS = "…" // U+2026
-
- def fixCtx(ctx: String): String = {
- val ctxLen = ctx.length()
- if(ctxLen == MaxCtxLength) {
- ctx
- } else if(ctxLen > MaxCtxLength) {
- ELLIPSIS + ctx.substring(ctxLen - MaxCtxLength + 1, ctxLen)
- } else {
- val buffer = new java.lang.StringBuilder(MaxCtxLength)
- val prefixLen = MaxCtxLength - ctxLen
- var _i = 0
- while(_i < prefixLen) {
- buffer.append(' ')
- _i = _i + 1
- }
- buffer.append(ctx)
- buffer.toString
- }
- }
-
- def fromOther(clogOpt: Option[ContextualLogger], logger: Logger, fmt: String, args: Any*): ContextualLogger = {
- clogOpt match {
- case Some(clog) ⇒
- new ContextualLogger(clog.logger, fmt, args:_*).indentAs(clog)
-
- case None ⇒
- new ContextualLogger(logger, fmt, args:_*)
- }
- }
-}
logger.error(message + "\n{}", chainOfCausesForLogging(t))
logger.error(message, t)
}
+
+ @inline
+ final def Debug(logger: Logger, fmt: String, args: Any*): Unit = {
+ if(logger.isDebugEnabled) {
+ logger.debug(fmt.format(args:_*))
+ }
+ }
+
+ @inline
+ final def Info(logger: Logger, fmt: String, args: Any*): Unit = {
+ if(logger.isInfoEnabled) {
+ logger.info(fmt.format(args:_*))
+ }
+ }
+
+ @inline
+ final def Warn(logger: Logger, fmt: String, args: Any*): Unit = {
+ if(logger.isWarnEnabled) {
+ logger.warn(fmt.format(args:_*))
+ }
+ }
+
+ @inline
+ final def Error(logger: Logger, fmt: String, args: Any*): Unit = {
+ if(logger.isErrorEnabled) {
+ logger.error(fmt.format(args:_*))
+ }
+ }
+
+ @inline
+ final def Error(logger: Logger, t: Throwable, fmt: String, args: Any*): Unit = {
+ if(logger.isErrorEnabled) {
+ logger.error(fmt.format(args:_*), t)
+ }
+ }
+
+ final def DebugMap[K, V](
+ logger: Logger,
+ name: String,
+ map: scala.collection.Map[K, V],
+ oneLineLimit: Int = 3
+ ): Unit = {
+
+ if(logger.isDebugEnabled) {
+ val mapSize = map.size
+ if(mapSize <= oneLineLimit) {
+ Debug(logger, "%s [#=%s] = %s", name, mapSize, map)
+ } else {
+ logger.debug("%s [#=%s]:", name, mapSize)
+ val maxKeySize = maxStringSize(map.keySet)
+ for((k, v) <- map) {
+ this.Debug(logger, "%s -> %s", rpad(k.toString, maxKeySize), v)
+ }
+ }
+ }
+ }
+
+ final def DebugSeq[T](logger: Logger, name: String, seq: scala.collection.Seq[T], oneLineLimit: Int = 3): Unit = {
+ if(logger.isDebugEnabled) {
+ val seqSize = seq.size
+ if(seqSize <= oneLineLimit) {
+ Debug(logger, "%s [#=%s] = %s", name, seqSize, seq)
+ } else {
+ Debug(logger, "%s [#=%s]: ", name, seqSize)
+ seq.foreach(Debug(logger, "%s", _))
+ }
+ }
+ }
+
+ final def DebugSet[T](logger: Logger, name: String, set: scala.collection.Set[T], oneLineLimit: Int = 3): Unit = {
+ if(logger.isDebugEnabled) {
+ val setSize = set.size
+ if(setSize <= oneLineLimit) {
+ Debug(logger, "%s [#=%s] = %s", name, setSize, set)
+ } else {
+ Debug(logger, "%s [#=%s]: ", name, setSize)
+ set.foreach(Debug(logger, "%s", _))
+ }
+ }
+ }
}
@Test
def testJson(): Unit = {
val json = policy.toJsonString
- val obj = PolicyModel.fromJsonString(json)
-
- println(json)
+ val obj = StdPolicy.fromJsonString(json)
assert(policy == obj)
}
+++ /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.user
-
-import gr.grnet.aquarium.store.memory.MemStoreProvider
-import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
-import gr.grnet.aquarium.simulation._
-import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
-import org.junit.{Assert, Ignore, Test}
-import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableChargingBehaviorAlgorithm, CostPolicyAlgorithmCompiler}
-import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
-import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.charging._
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, FullPriceTable, PolicyModel}
-import gr.grnet.aquarium.charging.reason.{NoSpecificChargingReason, MonthlyBillChargingReason}
-import gr.grnet.aquarium.charging.state.WorkingUserState
-import gr.grnet.aquarium.policy.FullPriceTable._
-import gr.grnet.aquarium.Timespan
-import gr.grnet.aquarium.simulation.AquariumSim
-import scala.Some
-import gr.grnet.aquarium.policy.EffectiveUnitPrice
-import gr.grnet.aquarium.policy.ResourceType
-import gr.grnet.aquarium.policy.StdUserAgreement
-import gr.grnet.aquarium.charging.state.UserStateBootstrap
-import gr.grnet.aquarium.policy.EffectivePriceTable
-import gr.grnet.aquarium.policy.StdPolicy
-import gr.grnet.aquarium.simulation.ClientSim
-
-
-/**
- *
- * @author Christos KK Loverdos <loverdos@gmail.com>
- */
-class UserStateComputationsTest extends Loggable {
- final val DoubleDelta = 0.001
-
- final val BandwidthUnitPrice = 3.3 //
- final val VMTimeUnitPrice = 1.5 //
- final val DiskspaceUnitPrice = 2.7 //
-
- final val OnOffUnitPrice = VMTimeUnitPrice
- final val ContinuousUnitPrice = DiskspaceUnitPrice
- final val DiscreteUnitPrice = BandwidthUnitPrice
-
- final val DefaultPolicy: PolicyModel = StdPolicy(
- id = "policy-1",
- parentID = None,
- validityTimespan = Timespan(0),
- resourceTypes = Set(
- ResourceType("vmtime", "Hr", classOf[VMChargingBehavior].getName),
- ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
- ),
- chargingBehaviors = Set(
- classOf[VMChargingBehavior].getName,
- classOf[ContinuousChargingBehavior].getName,
- classOf[OnceChargingBehavior].getName
- ),
- roleMapping = Map(
- "default" -> FullPriceTable(Map(
- "diskspace" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)),
- "vmtime" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil))
- ))
- )
- )
-
- val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel).
- update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
- build()
-
- val ResourceEventStore = aquarium.resourceEventStore
-
- val ChargingService = aquarium.chargingService
-
- val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
- def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
- hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
-
- final val creditsForDiskspace = creditsForContinuous(_, _)
-
- def creditsForDiscrete(currentValue: Double) =
- currentValue * DiscreteUnitPrice
-
- final val creditsForBandwidth = creditsForDiscrete(_)
-
- def creditsForOnOff(timeDelta: Double) =
- hrs(timeDelta) * OnOffUnitPrice
-
- final val creditsForVMTime = creditsForOnOff(_)
-
- @inline private[this]
- def hrs(millis: Double) = millis / 1000 / 60 / 60
-
- def apply(vars: Map[ChargingInput, Any]): Double = {
- vars.apply(ChargingBehaviorNameInput) match {
- case ChargingBehaviorAliases.continuous ⇒
- val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
- val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
- val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
-
- Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
-
- creditsForContinuous(timeDelta, oldTotalAmount)
-
- case ChargingBehaviorAliases.discrete ⇒
- val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
- val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
-
- Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
-
- creditsForDiscrete(currentValue)
-
- case ChargingBehaviorAliases.vmtime ⇒
- val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
- val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
-
- Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
-
- creditsForOnOff(timeDelta)
-
- case ChargingBehaviorAliases.once ⇒
- val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
- currentValue
-
- case name ⇒
- throw new AquariumException("Unknown cost policy %s".format(name))
- }
- }
-
- override def toString = "DefaultAlgorithm(%s)".format(
- Map(
- ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
- ChargingBehaviorAliases.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
- ChargingBehaviorAliases.vmtime -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
- ChargingBehaviorAliases.once -> "currentValue"))
- }
-
- val DefaultCompiler = new CostPolicyAlgorithmCompiler {
- def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
- DefaultAlgorithm
- }
- }
- //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
-
- val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
-
- // For this to work, the definitions must match those in the YAML above.
- // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
- val VMTimeResourceSim = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
- val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
-
- // There are two client services, synnefo and pithos.
- val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
- val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
- val Pithos = ClientSim("pithos" )(TheUIDGenerator)
-
- val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
- val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate
-
- val BillingMonthInfoJan = {
- val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
- BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
- }
- val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1))
- val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1))
-
- // Store the default policy
- val policyDateCalc = StartOfBillingYearDateCalc.copy
- val policyOccurredMillis = policyDateCalc.toMillis
- val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
- val policyValidToMillis = policyDateCalc.copy.goNextYear.toMillis
-
- aquarium.policyStore.insertPolicy(DefaultPolicy)
-
- val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim), aquarium.resourceEventStore)
- val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
-
- val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate)
-
-// val InitialUserState = UserState.createInitialUserState(
-// userID = UserCKKL.userID,
-// userCreationMillis = UserCreationDate.getTime,
-// totalCredits = 0.0,
-// initialRole = "default",
-// initialAgreement = DSLAgreement.DefaultAgreementName
-// )
-
- val UserStateBootstrapper = UserStateBootstrap(
- userID = UserCKKL.userID,
- userCreationMillis = UserCreationDate.getTime(),
- initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef),
- initialCredits = 0.0
- )
-
- // By convention
- // - synnefo is for VMTime and Bandwidth
- // - pithos is for Diskspace
- val VMTimeInstanceSim = VMTimeResourceSim.newInstance ("VM.1", UserCKKL, Synnefo)
- val DiskInstanceSim = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
-
- private[this]
- def showUserState(clog: ContextualLogger, workingUserState: WorkingUserState) {
-// val id = workingUserState.id
-// val parentId = workingUserState.parentUserStateIDInStore
-// val credits = workingUserState.totalCredits
-// val newWalletEntries = workingUserState.newWalletEntries.map(_.toDebugString)
-// val changeReason = workingUserState.lastChangeReason
-// val implicitlyIssued = workingUserState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
-// val latestResourceEvents = workingUserState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
-//
-// clog.debug("_id = %s", id)
-// clog.debug("parentId = %s", parentId)
-// clog.debug("credits = %s", credits)
-// clog.debug("changeReason = %s", changeReason)
-// clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
-// clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
-// clog.debugSeq("newWalletEntries", newWalletEntries, 0)
- }
-
- private[this]
- def showResourceEvents(clog: ContextualLogger): Unit = {
- clog.debug("")
- clog.begin("Events by OccurredMillis")
- clog.withIndent {
- for(event <- UserCKKL.myResourceEventsByOccurredDate) {
- clog.debug(event.toDebugString)
- }
- }
- clog.end("Events by OccurredMillis")
- clog.debug("")
- }
-
- private[this]
- def doFullMonthlyBilling(
- clog: ContextualLogger,
- billingMonthInfo: BillingMonthInfo,
- billingTimeMillis: Long) = {
-
- ChargingService.replayMonthChargingUpTo(
- billingMonthInfo,
- billingTimeMillis,
- UserStateBootstrapper,
- DefaultResourcesMap,
- MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo),
- aquarium.userStateStore.insertUserState,
- Some(clog)
- )
- }
-
- private[this]
- def expectCredits(
- clog: ContextualLogger,
- creditsConsumed: Double,
- workingUserState: WorkingUserState,
- accuracy: Double = 0.001
- ): Unit = {
-
- val computed = workingUserState.totalCredits
- Assert.assertEquals(-creditsConsumed, computed, accuracy)
-
- clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
- }
-
- private[this]
- def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
-
- private[this]
- def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
-
- /**
- * Test a sequence of ON, OFF vmtime events.
- */
- @Ignore
- @Test
- def testFullOnOff: Unit = {
- val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
- clog.begin()
-
- ResourceEventStore.clearResourceEvents()
- val OnOffDurationHrs = 10
- val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
-
- VMTimeInstanceSim.newONOFF(
- new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
- OnOffDurationHrs
- )
-
- val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
-
- showResourceEvents(clog)
-
- val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
- showUserState(clog, workingUserState)
-
- expectCredits(clog, credits, workingUserState)
-
- clog.end()
- }
-
- @Ignore
- @Test
- def testLonelyON: Unit = {
- val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
- clog.begin()
-
- ResourceEventStore.clearResourceEvents()
-
- val JanStart = new MutableDateCalc(2012, 01, 01)
- val JanEnd = JanStart.copy.goEndOfThisMonth
- val JanStartDate = JanStart.toDate
- val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
- val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
-
- VMTimeInstanceSim.newON(JanStartDate)
-
- val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
-
- showResourceEvents(clog)
-
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
- showUserState(clog, userState)
-
- expectCredits(clog, credits, userState)
-
- clog.end()
- }
-
-// @Ignore
- @Test
- def testOrphanOFF: Unit = {
- val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
- clog.begin()
-
- ResourceEventStore.clearResourceEvents()
-
- val JanStart = new MutableDateCalc(2012, 01, 01)
- val JanEnd = JanStart.copy.goEndOfThisMonth
- val JanStartDate = JanStart.toDate
- val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
- val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
-
- VMTimeInstanceSim.newOFF(JanStartDate)
-
- // This is an orphan event, so no credits will be charged
- val credits = 0
-
- showResourceEvents(clog)
-
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
- showUserState(clog, userState)
-
- expectCredits(clog, credits, userState)
-
- clog.end()
- }
-
- @Ignore
- @Test
- def testOne: Unit = {
- val clog = ContextualLogger.fromOther(None, logger, "testOne()")
- clog.begin()
-
- // Let's create our dates of interest
- val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
- val VMStartDate = VMStartDateCalc.toDate
-
- // Within January, create one VM ON-OFF ...
- VMTimeInstanceSim.newONOFF(VMStartDate, 9)
-
- val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
- val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
- val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
- val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
-
- // ... and two diskspace changes
- DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
- DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
-
- // ... and one "future" event
- DiskInstanceSim.consumeMB(
- StartOfBillingYearDateCalc.copy.
- goNextMonth.goPlusDays(6).
- goPlusHours(7).
- goPlusMinutes(7).
- goPlusSeconds(7).
- goPlusMillis(7).toDate,
- 777)
-
- showResourceEvents(clog)
-
- // Policy: from 2012-01-01 to Infinity
-
- clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1)
-
- val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
-
- showUserState(clog, userState)
-
- clog.end()
- }
-}
\ No newline at end of file