+/*
+ * 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.Configurator
import gr.grnet.aquarium.store.memory.MemStore
import gr.grnet.aquarium.util.date.MutableDateCalc
import gr.grnet.aquarium.logic.accounting.dsl._
import gr.grnet.aquarium.logic.accounting.{Policy, Accounting}
-import gr.grnet.aquarium.util.{Loggable, ContextualLogger, justForSure}
+import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
import gr.grnet.aquarium.simulation._
-import gr.grnet.aquarium.simulation.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
-import com.ckkloverdos.maybe.{Maybe, Just, NoVal}
-import gr.grnet.aquarium.logic.accounting.algorithm.SimpleCostPolicyAlgorithmCompiler
+import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
+import com.ckkloverdos.maybe.{Maybe, Just}
import org.junit.{Assert, Ignore, Test}
+import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler}
+import gr.grnet.aquarium.{AquariumException}
+import gr.grnet.aquarium.Aquarium.{Instance ⇒ AquariumInstance}
+import gr.grnet.aquarium.computation.{UserState, BillingMonthInfo, UserStateComputations}
+import gr.grnet.aquarium.computation.reason.MonthlyBillingCalculation
+import org.apache.ivy.util.Configurator
/**
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
class UserStateComputationsTest extends Loggable {
- val PolicyYAML = """
+ final val DoubleDelta = 0.001
+
+ final val BandwidthPriceUnit = 3.3 //
+ final val VMTimePriceUnit = 1.5 //
+ final val DiskspacePriceUnit = 2.7 //
+
+ final val OnOffPriceUnit = VMTimePriceUnit
+ final val ContinuousPriceUnit = DiskspacePriceUnit
+ final val DiscretePriceUnit = BandwidthPriceUnit
+
+ final val PolicyYAML = """
aquariumpolicy:
resources:
- resource:
pricelists:
- pricelist:
name: default
- bandwidth: 1.0
- vmtime: 1.0
- diskspace: 1.0
+ bandwidth: %s
+ vmtime: %s
+ diskspace: %s
effective:
from: 0
algorithm: default
pricelist: default
creditplan: default
- """
+ """.format(
+ BandwidthPriceUnit,
+ VMTimePriceUnit,
+ DiskspacePriceUnit
+ )
val Computations = new UserStateComputations
val DefaultPolicy = new DSL{} parse PolicyYAML
val DefaultAccounting = new Accounting{}
- val DefaultCompiler = SimpleCostPolicyAlgorithmCompiler
- val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
+
+ val DefaultAlgorithm = new ExecutableCostPolicyAlgorithm {
+ def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
+ hrs(timeDelta) * oldTotalAmount * ContinuousPriceUnit
+
+ final val creditsForDiskspace = creditsForContinuous(_, _)
+
+ def creditsForDiscrete(currentValue: Double) =
+ currentValue * DiscretePriceUnit
+
+ final val creditsForBandwidth = creditsForDiscrete(_)
+
+ def creditsForOnOff(timeDelta: Double) =
+ hrs(timeDelta) * OnOffPriceUnit
+
+ final val creditsForVMTime = creditsForOnOff(_)
+
+ @inline private[this]
+ def hrs(millis: Double) = millis / 1000 / 60 / 60
+
+ def apply(vars: Map[DSLCostPolicyVar, Any]): Double = {
+ vars.apply(DSLCostPolicyNameVar) match {
+ case DSLCostPolicyNames.continuous ⇒
+ val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+ val oldTotalAmount = vars(DSLOldTotalAmountVar).asInstanceOf[Double]
+ val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
+
+ Assert.assertEquals(ContinuousPriceUnit, unitPrice, DoubleDelta)
+
+ creditsForContinuous(timeDelta, oldTotalAmount)
+
+ case DSLCostPolicyNames.discrete ⇒
+ val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+ val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
+
+ Assert.assertEquals(DiscretePriceUnit, unitPrice, DoubleDelta)
+
+ creditsForDiscrete(currentValue)
+
+ case DSLCostPolicyNames.onoff ⇒
+ val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
+ val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
+
+ Assert.assertEquals(OnOffPriceUnit, unitPrice, DoubleDelta)
+
+ creditsForOnOff(timeDelta)
+
+ case DSLCostPolicyNames.once ⇒
+ val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
+ currentValue
+
+ case name ⇒
+ throw new AquariumException("Unknown cost policy %s".format(name))
+ }
+ }
+
+ override def toString = "DefaultAlgorithm(%s)".format(
+ Map(
+ DSLCostPolicyNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousPriceUnit),
+ DSLCostPolicyNames.discrete -> "currentValue * %s".format(DiscretePriceUnit),
+ DSLCostPolicyNames.onoff -> "hrs(timeDelta) * %s".format(OnOffPriceUnit),
+ DSLCostPolicyNames.once -> "currentValue"))
+ }
+
+ val DefaultCompiler = new CostPolicyAlgorithmCompiler {
+ def compile(definition: String): ExecutableCostPolicyAlgorithm = {
+ DefaultAlgorithm
+ }
+ }
+ //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
val VMTimeDSLResource = DefaultPolicy.findResource("vmtime").get
val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
// There are two client services, synnefo and pithos.
- val TheUIDGenerator: UIDGenerator = new ConcurrentVMLocalUIDGenerator
+ val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
val Pithos = ClientSim("pithos" )(TheUIDGenerator)
- val mc = Configurator.MasterConfigurator.withStoreProviderClass(classOf[MemStore])
- Policy.withConfigurator(mc)
- val StoreProvider = mc.storeProvider
+ val aquarium = AquariumInstance.withStoreProviderClass(classOf[MemStore])
+ Policy.withConfigurator(aquarium)
+ val StoreProvider = aquarium.storeProvider
val ResourceEventStore = StoreProvider.resourceEventStore
val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
val UserCKKL = Aquarium.newUser("CKKL", UserCreationDate)
- val InitialUserState = Computations.createInitialUserState(
- userId = UserCKKL.userId,
+ val InitialUserState = UserState.createInitialUserState(
+ userID = UserCKKL.userId,
userCreationMillis = UserCreationDate.getTime,
isActive = true,
credits = 0.0,
private[this]
def showUserState(clog: ContextualLogger, userState: UserState) {
- val id = userState.id
+ val id = userState._id
val parentId = userState.parentUserStateId
val credits = userState.creditsSnapshot.creditAmount
- val newWalletEntries = userState.newWalletEntries
- val changeReasonCode = userState.lastChangeReasonCode
+ val newWalletEntries = userState.newWalletEntries.map(_.toDebugString)
val changeReason = userState.lastChangeReason
- userState.implicitlyIssuedSnapshot
+ val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString())
+ val latestResourceEvents = userState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString())
- clog.indent()
clog.debug("_id = %s", id)
clog.debug("parentId = %s", parentId)
clog.debug("credits = %s", credits)
- clog.debug("changeReasonCode = %s", changeReasonCode)
clog.debug("changeReason = %s", changeReason)
- clog.debugSeq("newWalletEntries", newWalletEntries.map(_.toDebugString), 0)
- clog.unindent()
+ clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
+ clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
+ clog.debugSeq("newWalletEntries", newWalletEntries, 0)
}
private[this]
DefaultAccounting,
DefaultCompiler,
MonthlyBillingCalculation(billingMonthInfo),
- Just(clog)
+ Some(clog)
)
}
def justUserState(userStateM: Maybe[UserState]): UserState = {
userStateM match {
case Just(userState) ⇒ userState
- case _ ⇒ throw new Exception("Unexpected %s".format(userStateM))
+ case _ ⇒ throw new AquariumException("Unexpected %s".format(userStateM))
}
}
/**
* Test a sequence of ON, OFF vmtime events.
*/
- @Test @Ignore
- def testFullOnOff_FullMonthBilling: Unit = {
- val clog = ContextualLogger.fromOther(NoVal, logger, "testFullOnOff()")
+ @Ignore
+ @Test
+ def testFullOnOff: Unit = {
+ val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
+ clog.begin()
ResourceEventStore.clearResourceEvents()
val OnOffDurationHrs = 10
OnOffDurationHrs
)
- // Make a value map for the billable OFF event
- val valueMap = OnOffCostPolicy.makeValueMap(
- totalCredits = 0,
- oldTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
- newTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
- timeDelta = OnOffDurationMillis,
- previousValue = OnOffCostPolicyValues.ON,
- currentValue = OnOffCostPolicyValues.OFF,
- unitPrice = DefaultPolicy.findPriceList("default").get.prices(VMTimeDSLResource)
- )
-
- val credits = justForSure(DefaultAlgorithm(valueMap)).get
+ val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
showResourceEvents(clog)
- val userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
- val userState = justUserState(userStateM)
-
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
showUserState(clog, userState)
expectCredits(clog, credits, userState)
+
+ clog.end()
}
+ @Ignore
@Test
- def testLonelyON_FullMonthBilling: Unit = {
- val clog = ContextualLogger.fromOther(NoVal, logger, "testLonelyON()")
+ def testLonelyON: Unit = {
+ val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
+ clog.begin()
ResourceEventStore.clearResourceEvents()
val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
- VMTimeInstanceSim.newON(
- JanStartDate
- )
+ VMTimeInstanceSim.newON(JanStartDate)
- // Make a value map for the billable *implicit* OFF event
- val valueMap = OnOffCostPolicy.makeValueMap(
- totalCredits = 0,
- oldTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
- newTotalAmount = OnOffCostPolicy.getResourceInstanceUndefinedAmount,
- timeDelta = OnOffImplicitDurationMillis,
- previousValue = OnOffCostPolicyValues.ON,
- currentValue = OnOffCostPolicyValues.OFF,
- unitPrice = DefaultPolicy.findPriceList("default").get.prices(VMTimeDSLResource)
- )
+ val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
+
+ showResourceEvents(clog)
+
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
+ showUserState(clog, userState)
+
+ expectCredits(clog, credits, userState)
- val credits = justForSure(DefaultAlgorithm(valueMap)).get
+ 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 userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
- val userState = justUserState(userStateM)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
showUserState(clog, userState)
expectCredits(clog, credits, userState)
+
+ clog.end()
}
@Ignore
@Test
def testOne: Unit = {
- val clog = ContextualLogger.fromOther(NoVal, logger, "testOne()")
+ val clog = ContextualLogger.fromOther(None, logger, "testOne()")
+ clog.begin()
// Let's create our dates of interest
val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
clog.debugMap("DefaultResourcesMap", DefaultResourcesMap.map, 1)
- val userStateM = doFullMonthlyBilling(clog, BillingMonthInfoJan)
- val userState = justUserState(userStateM)
+ val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan)
+
showUserState(clog, userState)
+
+ clog.end()
}
}
\ No newline at end of file