From 684875dcd6fe25463f1c5c8c2ca637caf01d400d Mon Sep 17 00:00:00 2001 From: Christos KK Loverdos Date: Fri, 17 Aug 2012 16:37:40 +0300 Subject: [PATCH] WIP charging behavior --- .../aquarium/actor/service/user/UserActor.scala | 7 +- .../grnet/aquarium/charging/ChargingBehavior.scala | 478 +---------------- .../charging/ChargingBehaviorAliases.scala | 1 - .../charging/ChargingBehaviorSkeleton.scala | 536 ++++++++++++++++++++ .../grnet/aquarium/charging/ChargingService.scala | 105 ++-- .../charging/ContinuousChargingBehavior.scala | 2 +- .../charging/OnOffPolicyResourceState.scala | 66 --- .../aquarium/charging/OnceChargingBehavior.scala | 2 +- .../aquarium/charging/VMChargingBehavior.scala | 5 +- .../aquarium/charging/reason/ChargingReason.scala | 2 +- .../computation/TimeslotComputations.scala | 25 +- .../gr/grnet/aquarium/policy/PolicyModel.scala | 8 +- .../scala/gr/grnet/aquarium/policy/StdPolicy.scala | 7 + .../gr/grnet/aquarium/util/ContextualLogger.scala | 272 ---------- .../scala/gr/grnet/aquarium/util/LogHelpers.scala | 80 +++ .../gr/grnet/aquarium/policy/StdPolicyTest.scala | 4 +- .../aquarium/user/UserStateComputationsTest.scala | 437 ---------------- 17 files changed, 693 insertions(+), 1344 deletions(-) create mode 100644 src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala delete mode 100644 src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala delete mode 100644 src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala delete mode 100644 src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala index 65d14ff..b31ea37 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala @@ -232,8 +232,7 @@ class UserActor extends ReflectiveRoleableActor { this._userStateBootstrap, aquarium.currentResourceTypesMap, InitialUserActorSetup(), - aquarium.userStateStore.insertUserState, - None + aquarium.userStateStore.insertUserState ) // Final touch: Update agreement history in the working user state. @@ -387,8 +386,7 @@ class UserActor extends ReflectiveRoleableActor { this._userStateBootstrap, currentResourcesMap, chargingReason, - stdUserStateStoreFunc, - None + stdUserStateStoreFunc ) updateLatestResourceEventIDFrom(rcEvent) @@ -401,7 +399,6 @@ class UserActor extends ReflectiveRoleableActor { this._workingUserState, chargingReason, nowBillingMonthInfo, - None, true ) diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala index 99e9d39..7e80e69 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala @@ -35,241 +35,27 @@ 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 */ -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, @@ -280,33 +66,11 @@ abstract class ChargingBehavior( 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( @@ -318,226 +82,14 @@ abstract class ChargingBehavior( 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 { @@ -560,7 +112,7 @@ object ChargingBehavior { currentValue: Double, unitPrice: Double ): Map[ChargingInput, Any] = { - + val inputs = chargingBehavior.inputs var map = Map[ChargingInput, Any]() diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala index 5d5c451..ac846b0 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorAliases.scala @@ -42,7 +42,6 @@ package gr.grnet.aquarium.charging object ChargingBehaviorAliases { final val vmtime = "vmtime" - final val discrete = "discrete" final val continuous = "continuous" final val once = "once" } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala new file mode 100644 index 0000000..993fe62 --- /dev/null +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala @@ -0,0 +1,536 @@ +/* + * 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 + */ + +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 +} diff --git a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala index 91042b5..d87035e 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala @@ -39,7 +39,10 @@ import gr.grnet.aquarium.event.model.resource.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} @@ -73,26 +76,16 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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) @@ -107,7 +100,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo // 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 } @@ -120,7 +113,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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 @@ -130,13 +123,12 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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) } @@ -149,10 +141,8 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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. @@ -169,21 +159,18 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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) } } @@ -195,15 +182,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo * @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 @@ -225,11 +210,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo workingUserState.workingAgreementHistory.toAgreementHistory, workingUserState.getChargingDataForResourceEvent(resourceAndInstanceInfo), workingUserState.totalCredits, - workingUserState.walletEntries += _, - clogOpt + workingUserState.walletEntries += _ ) - if(updateLatestMiilis) { + if(updateLatestMillis) { workingUserState.latestUpdateMillis = TimeHelpers.nowMillis() } @@ -242,8 +226,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo resourceEvents: Traversable[ResourceEventModel], workingUserState: WorkingUserState, chargingReason: ChargingReason, - billingMonthInfo: BillingMonthInfo, - clogOpt: Option[ContextualLogger] = None + billingMonthInfo: BillingMonthInfo ): Unit = { var _counter = 0 @@ -253,7 +236,6 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo workingUserState, chargingReason, billingMonthInfo, - clogOpt, false ) @@ -270,8 +252,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo billingMonthInfo: BillingMonthInfo, defaultResourceTypesMap: Map[String, ResourceType], chargingReason: ChargingReason, - userStateRecorder: UserStateModel ⇒ UserStateModel, - clogOpt: Option[ContextualLogger] + userStateRecorder: UserStateModel ⇒ UserStateModel ): WorkingUserState = { replayMonthChargingUpTo( @@ -280,8 +261,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo userStateBootstrap, defaultResourceTypesMap, chargingReason, - userStateRecorder, - clogOpt + userStateRecorder ) } @@ -295,7 +275,6 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo * @param resourceTypesMap * @param chargingReason * @param userStateRecorder - * @param clogOpt * @return */ def replayMonthChargingUpTo( @@ -304,22 +283,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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 @@ -328,8 +298,7 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo userStateBootstrap, resourceTypesMap, chargingReason, - userStateRecorder, - clogSome + userStateRecorder ) // FIXME the below comments @@ -337,10 +306,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo // 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 @@ -350,14 +319,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo billingEndTimeMillis // to requested time ) { currentResourceEvent ⇒ - clog.debug("Processing %s".format(currentResourceEvent)) + Debug(logger, "Processing %s", currentResourceEvent) processResourceEvent( currentResourceEvent, workingUserState, chargingReason, billingMonthInfo, - clogSome, false ) @@ -368,7 +336,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo 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 @@ -379,10 +350,10 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo ) 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 @@ -395,15 +366,13 @@ final class ChargingService extends AquariumAwareSkeleton with Lifecycle with Lo theirImplicitEnds, specialWorkingUserState, chargingReason, - billingMonthInfo, - clogSome + billingMonthInfo ) workingUserState.walletEntries ++= specialWorkingUserState.walletEntries workingUserState.totalCredits = specialWorkingUserState.totalCredits } - clog.end() workingUserState } } diff --git a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala index 7241a77..0dcfeba 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala @@ -48,7 +48,7 @@ import gr.grnet.aquarium.logic.accounting.dsl.Timeslot * @author Christos KK Loverdos */ final class ContinuousChargingBehavior - extends ChargingBehavior( + extends ChargingBehaviorSkeleton( ChargingBehaviorAliases.continuous, Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) { diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala b/src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala deleted file mode 100644 index f3573ca..0000000 --- a/src/main/scala/gr/grnet/aquarium/charging/OnOffPolicyResourceState.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2011-2012 GRNET S.A. All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and - * documentation are those of the authors and should not be - * interpreted as representing official policies, either expressed - * or implied, of GRNET S.A. - */ - -package gr.grnet.aquarium.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 - */ -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" - } -} - diff --git a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala index bc8327c..cbb66bc 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala @@ -49,7 +49,7 @@ import gr.grnet.aquarium.policy.FullPriceTable * @author Christos KK Loverdos */ final class OnceChargingBehavior - extends ChargingBehavior( + extends ChargingBehaviorSkeleton( ChargingBehaviorAliases.once, Set(ChargingBehaviorNameInput, CurrentValueInput)) { diff --git a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala index 1fa1237..0f43afb 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala @@ -35,12 +35,11 @@ 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. @@ -48,7 +47,7 @@ import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable} * @author Christos KK Loverdos */ final class VMChargingBehavior - extends ChargingBehavior( + extends ChargingBehaviorSkeleton( ChargingBehaviorAliases.vmtime, Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput), List(List(Power.powerOn, Power.powerOff))) { diff --git a/src/main/scala/gr/grnet/aquarium/charging/reason/ChargingReason.scala b/src/main/scala/gr/grnet/aquarium/charging/reason/ChargingReason.scala index 50c8f4c..b7e8388 100644 --- a/src/main/scala/gr/grnet/aquarium/charging/reason/ChargingReason.scala +++ b/src/main/scala/gr/grnet/aquarium/charging/reason/ChargingReason.scala @@ -41,7 +41,7 @@ import gr.grnet.aquarium.util.shortClassNameOf /** * 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], diff --git a/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala index c0da20d..e9e932f 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/TimeslotComputations.scala @@ -36,16 +36,13 @@ 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. @@ -67,8 +64,7 @@ object TimeslotComputations extends Loggable { 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 @@ -91,12 +87,9 @@ object TimeslotComputations extends Loggable { 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)} @@ -107,12 +100,9 @@ object TimeslotComputations extends Loggable { 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 @@ -125,7 +115,7 @@ object TimeslotComputations extends Loggable { // 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. @@ -145,8 +135,7 @@ object TimeslotComputations extends Loggable { policy, userAgreement, resourceType, - effectivePriceTableSelector, - Some(clog) + effectivePriceTableSelector ) // Now, the timeslots must be the same diff --git a/src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala b/src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala index 2246a63..50000cb 100644 --- a/src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala +++ b/src/main/scala/gr/grnet/aquarium/policy/PolicyModel.scala @@ -111,12 +111,10 @@ trait PolicyModel extends Ordered[PolicyModel] with JsonSupport { 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)) - } } diff --git a/src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala b/src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala index 0b0667b..d18faf6 100644 --- a/src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala +++ b/src/main/scala/gr/grnet/aquarium/policy/StdPolicy.scala @@ -36,6 +36,7 @@ package gr.grnet.aquarium.policy import gr.grnet.aquarium.Timespan +import gr.grnet.aquarium.converter.{JsonTextFormat, StdConverters} /** * Standard implementation of Aquarium policy model. @@ -54,3 +55,9 @@ case class StdPolicy( def idInStore = Some(id) } + +object StdPolicy { + def fromJsonString(json: String): StdPolicy = { + StdConverters.AllConverters.convertEx[StdPolicy](JsonTextFormat(json)) + } +} diff --git a/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala b/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala deleted file mode 100644 index c1f9a8b..0000000 --- a/src/main/scala/gr/grnet/aquarium/util/ContextualLogger.scala +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2011-2012 GRNET S.A. All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and - * documentation are those of the authors and should not be - * interpreted as representing official policies, either expressed - * or implied, of GRNET S.A. - */ - -package gr.grnet.aquarium.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 - */ -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 - */ -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:_*) - } - } -} diff --git a/src/main/scala/gr/grnet/aquarium/util/LogHelpers.scala b/src/main/scala/gr/grnet/aquarium/util/LogHelpers.scala index d05ada7..2065d5f 100644 --- a/src/main/scala/gr/grnet/aquarium/util/LogHelpers.scala +++ b/src/main/scala/gr/grnet/aquarium/util/LogHelpers.scala @@ -84,4 +84,84 @@ object LogHelpers { 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", _)) + } + } + } } diff --git a/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala b/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala index 3dc1160..4f228b1 100644 --- a/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala +++ b/src/test/scala/gr/grnet/aquarium/policy/StdPolicyTest.scala @@ -81,9 +81,7 @@ class StdPolicyTest { @Test def testJson(): Unit = { val json = policy.toJsonString - val obj = PolicyModel.fromJsonString(json) - - println(json) + val obj = StdPolicy.fromJsonString(json) assert(policy == obj) } diff --git a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala b/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala deleted file mode 100644 index da4c635..0000000 --- a/src/test/scala/gr/grnet/aquarium/user/UserStateComputationsTest.scala +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright 2011-2012 GRNET S.A. All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and - * documentation are those of the authors and should not be - * interpreted as representing official policies, either expressed - * or implied, of GRNET S.A. - */ - -package gr.grnet.aquarium.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 - */ -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 -- 1.7.10.4