2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and
31 * documentation are those of the authors and should not be
32 * interpreted as representing official policies, either expressed
33 * or implied, of GRNET S.A.
36 package gr.grnet.aquarium.user
38 import gr.grnet.aquarium.store.memory.MemStoreProvider
39 import gr.grnet.aquarium.logic.accounting.dsl._
40 import gr.grnet.aquarium.logic.accounting.Policy
41 import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
42 import gr.grnet.aquarium.simulation._
43 import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
44 import org.junit.{Assert, Ignore, Test}
45 import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableCostPolicyAlgorithm, CostPolicyAlgorithmCompiler}
46 import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
47 import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, MonthlyBillingCalculation}
48 import gr.grnet.aquarium.util.date.MutableDateCalc
49 import gr.grnet.aquarium.computation.BillingMonthInfo
50 import gr.grnet.aquarium.computation.state.{UserStateBootstrap, UserState}
55 * @author Christos KK Loverdos <loverdos@gmail.com>
57 class UserStateComputationsTest extends Loggable {
58 final val DoubleDelta = 0.001
60 final val BandwidthPriceUnit = 3.3 //
61 final val VMTimePriceUnit = 1.5 //
62 final val DiskspacePriceUnit = 2.7 //
64 final val OnOffPriceUnit = VMTimePriceUnit
65 final val ContinuousPriceUnit = DiskspacePriceUnit
66 final val DiscretePriceUnit = BandwidthPriceUnit
68 final val PolicyYAML = """
81 descriminatorfield: vmid
86 costpolicy: continuous
95 bandwidth: function bandwidth() {return 1;}
96 vmtime: function vmtime() {return 1;}
97 diskspace: function diskspace() {return 1;}
130 val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties).
131 update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
134 Policy.withConfigurator(aquarium) // FIXME
136 val ResourceEventStore = aquarium.resourceEventStore
138 val Computations = aquarium.userStateComputations
141 val DefaultPolicy = DSL parse PolicyYAML
143 val DefaultAlgorithm = new ExecutableCostPolicyAlgorithm {
144 def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
145 hrs(timeDelta) * oldTotalAmount * ContinuousPriceUnit
147 final val creditsForDiskspace = creditsForContinuous(_, _)
149 def creditsForDiscrete(currentValue: Double) =
150 currentValue * DiscretePriceUnit
152 final val creditsForBandwidth = creditsForDiscrete(_)
154 def creditsForOnOff(timeDelta: Double) =
155 hrs(timeDelta) * OnOffPriceUnit
157 final val creditsForVMTime = creditsForOnOff(_)
159 @inline private[this]
160 def hrs(millis: Double) = millis / 1000 / 60 / 60
162 def apply(vars: Map[DSLCostPolicyVar, Any]): Double = {
163 vars.apply(DSLCostPolicyNameVar) match {
164 case DSLCostPolicyNames.continuous ⇒
165 val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
166 val oldTotalAmount = vars(DSLOldTotalAmountVar).asInstanceOf[Double]
167 val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
169 Assert.assertEquals(ContinuousPriceUnit, unitPrice, DoubleDelta)
171 creditsForContinuous(timeDelta, oldTotalAmount)
173 case DSLCostPolicyNames.discrete ⇒
174 val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
175 val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
177 Assert.assertEquals(DiscretePriceUnit, unitPrice, DoubleDelta)
179 creditsForDiscrete(currentValue)
181 case DSLCostPolicyNames.onoff ⇒
182 val unitPrice = vars(DSLUnitPriceVar).asInstanceOf[Double]
183 val timeDelta = vars(DSLTimeDeltaVar).asInstanceOf[Double]
185 Assert.assertEquals(OnOffPriceUnit, unitPrice, DoubleDelta)
187 creditsForOnOff(timeDelta)
189 case DSLCostPolicyNames.once ⇒
190 val currentValue = vars(DSLCurrentValueVar).asInstanceOf[Double]
194 throw new AquariumException("Unknown cost policy %s".format(name))
198 override def toString = "DefaultAlgorithm(%s)".format(
200 DSLCostPolicyNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousPriceUnit),
201 DSLCostPolicyNames.discrete -> "currentValue * %s".format(DiscretePriceUnit),
202 DSLCostPolicyNames.onoff -> "hrs(timeDelta) * %s".format(OnOffPriceUnit),
203 DSLCostPolicyNames.once -> "currentValue"))
206 val DefaultCompiler = new CostPolicyAlgorithmCompiler {
207 def compile(definition: String): ExecutableCostPolicyAlgorithm = {
211 //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
213 val VMTimeDSLResource = DefaultPolicy.findResource("vmtime").get
215 // For this to work, the definitions must match those in the YAML above.
216 // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
217 val VMTimeResourceSim = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
218 val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
219 val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
221 // There are two client services, synnefo and pithos.
222 val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
223 val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
224 val Pithos = ClientSim("pithos" )(TheUIDGenerator)
226 val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
227 val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate
229 val BillingMonthInfoJan = {
230 val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
231 BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
233 val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1))
234 val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1))
236 // Store the default policy
237 val policyDateCalc = StartOfBillingYearDateCalc.copy
238 val policyOccurredMillis = policyDateCalc.toMillis
239 val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
240 val policyValidToMillis = policyDateCalc.copy.goNextYear.toMillis
241 aquarium.policyStore.storePolicyEntry(DefaultPolicy.toPolicyEntry(policyOccurredMillis, policyValidFromMillis,
242 policyValidToMillis))
244 val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore)
245 val DefaultResourcesMap = AquariumSim_.resourcesMap
247 val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate)
249 // val InitialUserState = UserState.createInitialUserState(
250 // userID = UserCKKL.userID,
251 // userCreationMillis = UserCreationDate.getTime,
252 // totalCredits = 0.0,
253 // initialRole = "default",
254 // initialAgreement = DSLAgreement.DefaultAgreementName
257 val UserStateBootstrapper = UserStateBootstrap(
258 userID = UserCKKL.userID,
259 userCreationMillis = UserCreationDate.getTime(),
260 initialRole = "default",
261 initialAgreement = DSLAgreement.DefaultAgreementName,
266 // - synnefo is for VMTime and Bandwidth
267 // - pithos is for Diskspace
268 val VMTimeInstanceSim = VMTimeResourceSim.newInstance ("VM.1", UserCKKL, Synnefo)
269 val BandwidthInstanceSim = BandwidthResourceSim.newInstance("3G.1", UserCKKL, Synnefo)
270 val DiskInstanceSim = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
273 def showUserState(clog: ContextualLogger, userState: UserState) {
274 val id = userState._id
275 val parentId = userState.parentUserStateIDInStore
276 val credits = userState.totalCredits
277 val newWalletEntries = userState.newWalletEntries.map(_.toDebugString)
278 val changeReason = userState.lastChangeReason
279 val implicitlyIssued = userState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
280 val latestResourceEvents = userState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
282 clog.debug("_id = %s", id)
283 clog.debug("parentId = %s", parentId)
284 clog.debug("credits = %s", credits)
285 clog.debug("changeReason = %s", changeReason)
286 clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
287 clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
288 clog.debugSeq("newWalletEntries", newWalletEntries, 0)
292 def showResourceEvents(clog: ContextualLogger): Unit = {
294 clog.begin("Events by OccurredMillis")
296 for(event <- UserCKKL.myResourceEventsByOccurredDate) {
297 clog.debug(event.toDebugString)
300 clog.end("Events by OccurredMillis")
305 def doFullMonthlyBilling(
306 clog: ContextualLogger,
307 billingMonthInfo: BillingMonthInfo,
308 billingTimeMillis: Long) = {
310 Computations.doMonthBillingUpTo(
313 UserStateBootstrapper,
315 MonthlyBillingCalculation(NoSpecificChangeReason(), billingMonthInfo),
316 aquarium.userStateStore.insertUserState,
323 clog: ContextualLogger,
324 creditsConsumed: Double,
325 userState: UserState,
326 accuracy: Double = 0.001
329 val computed = userState.totalCredits
330 Assert.assertEquals(-creditsConsumed, computed, accuracy)
332 clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
336 def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
339 def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
342 * Test a sequence of ON, OFF vmtime events.
346 def testFullOnOff: Unit = {
347 val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
350 ResourceEventStore.clearResourceEvents()
351 val OnOffDurationHrs = 10
352 val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
354 VMTimeInstanceSim.newONOFF(
355 new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
359 val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
361 showResourceEvents(clog)
363 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
365 showUserState(clog, userState)
367 expectCredits(clog, credits, userState)
374 def testLonelyON: Unit = {
375 val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
378 ResourceEventStore.clearResourceEvents()
380 val JanStart = new MutableDateCalc(2012, 01, 01)
381 val JanEnd = JanStart.copy.goEndOfThisMonth
382 val JanStartDate = JanStart.toDate
383 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
384 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
386 VMTimeInstanceSim.newON(JanStartDate)
388 val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
390 showResourceEvents(clog)
392 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
394 showUserState(clog, userState)
396 expectCredits(clog, credits, userState)
403 def testOrphanOFF: Unit = {
404 val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
407 ResourceEventStore.clearResourceEvents()
409 val JanStart = new MutableDateCalc(2012, 01, 01)
410 val JanEnd = JanStart.copy.goEndOfThisMonth
411 val JanStartDate = JanStart.toDate
412 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
413 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
415 VMTimeInstanceSim.newOFF(JanStartDate)
417 // This is an orphan event, so no credits will be charged
420 showResourceEvents(clog)
422 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
424 showUserState(clog, userState)
426 expectCredits(clog, credits, userState)
433 def testOne: Unit = {
434 val clog = ContextualLogger.fromOther(None, logger, "testOne()")
437 // Let's create our dates of interest
438 val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
439 val VMStartDate = VMStartDateCalc.toDate
441 // Within January, create one VM ON-OFF ...
442 VMTimeInstanceSim.newONOFF(VMStartDate, 9)
444 val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
445 val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
446 val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
447 val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
449 // ... and two diskspace changes
450 DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
451 DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
453 // 100MB 3G bandwidth
454 val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1)
455 BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0)
457 // ... and one "future" event
458 DiskInstanceSim.consumeMB(
459 StartOfBillingYearDateCalc.copy.
460 goNextMonth.goPlusDays(6).
464 goPlusMillis(7).toDate,
467 showResourceEvents(clog)
469 // Policy: from 2012-01-01 to Infinity
471 clog.debugMap("DefaultResourcesMap", DefaultResourcesMap.map, 1)
473 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
475 showUserState(clog, userState)