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.util.{Loggable, ContextualLogger}
40 import gr.grnet.aquarium.simulation._
41 import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
42 import org.junit.{Assert, Ignore, Test}
43 import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableChargingBehaviorAlgorithm, CostPolicyAlgorithmCompiler}
44 import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
45 import gr.grnet.aquarium.util.date.MutableDateCalc
46 import gr.grnet.aquarium.computation.BillingMonthInfo
47 import gr.grnet.aquarium.charging._
48 import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy, PolicyModel}
49 import gr.grnet.aquarium.Timespan
50 import gr.grnet.aquarium.charging.state.UserStateBootstrap
51 import gr.grnet.aquarium.charging.reason.{NoSpecificChargingReason, MonthlyBillChargingReason}
52 import gr.grnet.aquarium.charging.state.WorkingUserState
57 * @author Christos KK Loverdos <loverdos@gmail.com>
59 class UserStateComputationsTest extends Loggable {
60 final val DoubleDelta = 0.001
62 final val BandwidthUnitPrice = 3.3 //
63 final val VMTimeUnitPrice = 1.5 //
64 final val DiskspaceUnitPrice = 2.7 //
66 final val OnOffUnitPrice = VMTimeUnitPrice
67 final val ContinuousUnitPrice = DiskspaceUnitPrice
68 final val DiscreteUnitPrice = BandwidthUnitPrice
70 final val DefaultPolicy: PolicyModel = StdPolicy(
73 validityTimespan = Timespan(0),
75 ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
76 ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
77 ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
79 chargingBehaviors = Set(
80 classOf[DiscreteChargingBehavior].getName,
81 classOf[OnOffChargingBehavior].getName,
82 classOf[ContinuousChargingBehavior].getName,
83 classOf[OnceChargingBehavior].getName
86 "default" -> FullPriceTable(Map(
87 "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(BandwidthUnitPrice, None) :: Nil),
88 "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(VMTimeUnitPrice, None) :: Nil),
89 "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(DiskspaceUnitPrice, None) :: Nil)
94 val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel).
95 update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
98 val ResourceEventStore = aquarium.resourceEventStore
100 val ChargingService = aquarium.chargingService
102 val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
103 def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
104 hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
106 final val creditsForDiskspace = creditsForContinuous(_, _)
108 def creditsForDiscrete(currentValue: Double) =
109 currentValue * DiscreteUnitPrice
111 final val creditsForBandwidth = creditsForDiscrete(_)
113 def creditsForOnOff(timeDelta: Double) =
114 hrs(timeDelta) * OnOffUnitPrice
116 final val creditsForVMTime = creditsForOnOff(_)
118 @inline private[this]
119 def hrs(millis: Double) = millis / 1000 / 60 / 60
121 def apply(vars: Map[ChargingInput, Any]): Double = {
122 vars.apply(ChargingBehaviorNameInput) match {
123 case ChargingBehaviorAliases.continuous ⇒
124 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
125 val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
126 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
128 Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
130 creditsForContinuous(timeDelta, oldTotalAmount)
132 case ChargingBehaviorAliases.discrete ⇒
133 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
134 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
136 Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
138 creditsForDiscrete(currentValue)
140 case ChargingBehaviorAliases.onoff ⇒
141 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
142 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
144 Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
146 creditsForOnOff(timeDelta)
148 case ChargingBehaviorAliases.once ⇒
149 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
153 throw new AquariumException("Unknown cost policy %s".format(name))
157 override def toString = "DefaultAlgorithm(%s)".format(
159 ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
160 ChargingBehaviorAliases.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
161 ChargingBehaviorAliases.onoff -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
162 ChargingBehaviorAliases.once -> "currentValue"))
165 val DefaultCompiler = new CostPolicyAlgorithmCompiler {
166 def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
170 //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
172 val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
174 // For this to work, the definitions must match those in the YAML above.
175 // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
176 val VMTimeResourceSim = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
177 val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
178 val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
180 // There are two client services, synnefo and pithos.
181 val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
182 val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
183 val Pithos = ClientSim("pithos" )(TheUIDGenerator)
185 val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
186 val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate
188 val BillingMonthInfoJan = {
189 val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
190 BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
192 val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1))
193 val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1))
195 // Store the default policy
196 val policyDateCalc = StartOfBillingYearDateCalc.copy
197 val policyOccurredMillis = policyDateCalc.toMillis
198 val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
199 val policyValidToMillis = policyDateCalc.copy.goNextYear.toMillis
201 aquarium.policyStore.insertPolicy(DefaultPolicy)
203 val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore)
204 val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
206 val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate)
208 // val InitialUserState = UserState.createInitialUserState(
209 // userID = UserCKKL.userID,
210 // userCreationMillis = UserCreationDate.getTime,
211 // totalCredits = 0.0,
212 // initialRole = "default",
213 // initialAgreement = DSLAgreement.DefaultAgreementName
216 val UserStateBootstrapper = UserStateBootstrap(
217 userID = UserCKKL.userID,
218 userCreationMillis = UserCreationDate.getTime(),
219 initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef),
224 // - synnefo is for VMTime and Bandwidth
225 // - pithos is for Diskspace
226 val VMTimeInstanceSim = VMTimeResourceSim.newInstance ("VM.1", UserCKKL, Synnefo)
227 val BandwidthInstanceSim = BandwidthResourceSim.newInstance("3G.1", UserCKKL, Synnefo)
228 val DiskInstanceSim = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
231 def showUserState(clog: ContextualLogger, workingUserState: WorkingUserState) {
232 // val id = workingUserState.id
233 // val parentId = workingUserState.parentUserStateIDInStore
234 // val credits = workingUserState.totalCredits
235 // val newWalletEntries = workingUserState.newWalletEntries.map(_.toDebugString)
236 // val changeReason = workingUserState.lastChangeReason
237 // val implicitlyIssued = workingUserState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
238 // val latestResourceEvents = workingUserState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
240 // clog.debug("_id = %s", id)
241 // clog.debug("parentId = %s", parentId)
242 // clog.debug("credits = %s", credits)
243 // clog.debug("changeReason = %s", changeReason)
244 // clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
245 // clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
246 // clog.debugSeq("newWalletEntries", newWalletEntries, 0)
250 def showResourceEvents(clog: ContextualLogger): Unit = {
252 clog.begin("Events by OccurredMillis")
254 for(event <- UserCKKL.myResourceEventsByOccurredDate) {
255 clog.debug(event.toDebugString)
258 clog.end("Events by OccurredMillis")
263 def doFullMonthlyBilling(
264 clog: ContextualLogger,
265 billingMonthInfo: BillingMonthInfo,
266 billingTimeMillis: Long) = {
268 ChargingService.replayMonthChargingUpTo(
271 UserStateBootstrapper,
273 MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo),
274 aquarium.userStateStore.insertUserState,
281 clog: ContextualLogger,
282 creditsConsumed: Double,
283 workingUserState: WorkingUserState,
284 accuracy: Double = 0.001
287 val computed = workingUserState.totalCredits
288 Assert.assertEquals(-creditsConsumed, computed, accuracy)
290 clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
294 def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
297 def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
300 * Test a sequence of ON, OFF vmtime events.
304 def testFullOnOff: Unit = {
305 val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
308 ResourceEventStore.clearResourceEvents()
309 val OnOffDurationHrs = 10
310 val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
312 VMTimeInstanceSim.newONOFF(
313 new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
317 val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
319 showResourceEvents(clog)
321 val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
323 showUserState(clog, workingUserState)
325 expectCredits(clog, credits, workingUserState)
332 def testLonelyON: Unit = {
333 val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
336 ResourceEventStore.clearResourceEvents()
338 val JanStart = new MutableDateCalc(2012, 01, 01)
339 val JanEnd = JanStart.copy.goEndOfThisMonth
340 val JanStartDate = JanStart.toDate
341 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
342 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
344 VMTimeInstanceSim.newON(JanStartDate)
346 val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
348 showResourceEvents(clog)
350 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
352 showUserState(clog, userState)
354 expectCredits(clog, credits, userState)
361 def testOrphanOFF: Unit = {
362 val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
365 ResourceEventStore.clearResourceEvents()
367 val JanStart = new MutableDateCalc(2012, 01, 01)
368 val JanEnd = JanStart.copy.goEndOfThisMonth
369 val JanStartDate = JanStart.toDate
370 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
371 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
373 VMTimeInstanceSim.newOFF(JanStartDate)
375 // This is an orphan event, so no credits will be charged
378 showResourceEvents(clog)
380 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
382 showUserState(clog, userState)
384 expectCredits(clog, credits, userState)
391 def testOne: Unit = {
392 val clog = ContextualLogger.fromOther(None, logger, "testOne()")
395 // Let's create our dates of interest
396 val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
397 val VMStartDate = VMStartDateCalc.toDate
399 // Within January, create one VM ON-OFF ...
400 VMTimeInstanceSim.newONOFF(VMStartDate, 9)
402 val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
403 val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
404 val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
405 val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
407 // ... and two diskspace changes
408 DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
409 DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
411 // 100MB 3G bandwidth
412 val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1)
413 BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0)
415 // ... and one "future" event
416 DiskInstanceSim.consumeMB(
417 StartOfBillingYearDateCalc.copy.
418 goNextMonth.goPlusDays(6).
422 goPlusMillis(7).toDate,
425 showResourceEvents(clog)
427 // Policy: from 2012-01-01 to Infinity
429 clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1)
431 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
433 showUserState(clog, userState)