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, FullPriceTable, PolicyModel}
49 import gr.grnet.aquarium.charging.reason.{NoSpecificChargingReason, MonthlyBillChargingReason}
50 import gr.grnet.aquarium.charging.state.WorkingUserState
51 import gr.grnet.aquarium.policy.FullPriceTable._
52 import gr.grnet.aquarium.Timespan
53 import gr.grnet.aquarium.simulation.AquariumSim
55 import gr.grnet.aquarium.policy.EffectiveUnitPrice
56 import gr.grnet.aquarium.policy.ResourceType
57 import gr.grnet.aquarium.policy.StdUserAgreement
58 import gr.grnet.aquarium.charging.state.UserStateBootstrap
59 import gr.grnet.aquarium.policy.EffectivePriceTable
60 import gr.grnet.aquarium.policy.StdPolicy
61 import gr.grnet.aquarium.simulation.ClientSim
66 * @author Christos KK Loverdos <loverdos@gmail.com>
68 class UserStateComputationsTest extends Loggable {
69 final val DoubleDelta = 0.001
71 final val BandwidthUnitPrice = 3.3 //
72 final val VMTimeUnitPrice = 1.5 //
73 final val DiskspaceUnitPrice = 2.7 //
75 final val OnOffUnitPrice = VMTimeUnitPrice
76 final val ContinuousUnitPrice = DiskspaceUnitPrice
77 final val DiscreteUnitPrice = BandwidthUnitPrice
79 final val DefaultPolicy: PolicyModel = StdPolicy(
82 validityTimespan = Timespan(0),
84 ResourceType("vmtime", "Hr", classOf[VMChargingBehavior].getName),
85 ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
87 chargingBehaviors = Set(
88 classOf[VMChargingBehavior].getName,
89 classOf[ContinuousChargingBehavior].getName,
90 classOf[OnceChargingBehavior].getName
93 "default" -> FullPriceTable(Map(
94 "diskspace" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil)),
95 "vmtime" -> Map(DefaultSelectorKey -> EffectivePriceTable(EffectiveUnitPrice(0.01) :: Nil))
100 val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel).
101 update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
104 val ResourceEventStore = aquarium.resourceEventStore
106 val ChargingService = aquarium.chargingService
108 val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
109 def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
110 hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
112 final val creditsForDiskspace = creditsForContinuous(_, _)
114 def creditsForDiscrete(currentValue: Double) =
115 currentValue * DiscreteUnitPrice
117 final val creditsForBandwidth = creditsForDiscrete(_)
119 def creditsForOnOff(timeDelta: Double) =
120 hrs(timeDelta) * OnOffUnitPrice
122 final val creditsForVMTime = creditsForOnOff(_)
124 @inline private[this]
125 def hrs(millis: Double) = millis / 1000 / 60 / 60
127 def apply(vars: Map[ChargingInput, Any]): Double = {
128 vars.apply(ChargingBehaviorNameInput) match {
129 case ChargingBehaviorAliases.continuous ⇒
130 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
131 val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
132 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
134 Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
136 creditsForContinuous(timeDelta, oldTotalAmount)
138 case ChargingBehaviorAliases.discrete ⇒
139 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
140 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
142 Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
144 creditsForDiscrete(currentValue)
146 case ChargingBehaviorAliases.vmtime ⇒
147 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
148 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
150 Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
152 creditsForOnOff(timeDelta)
154 case ChargingBehaviorAliases.once ⇒
155 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
159 throw new AquariumException("Unknown cost policy %s".format(name))
163 override def toString = "DefaultAlgorithm(%s)".format(
165 ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
166 ChargingBehaviorAliases.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
167 ChargingBehaviorAliases.vmtime -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
168 ChargingBehaviorAliases.once -> "currentValue"))
171 val DefaultCompiler = new CostPolicyAlgorithmCompiler {
172 def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
176 //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
178 val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
180 // For this to work, the definitions must match those in the YAML above.
181 // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
182 val VMTimeResourceSim = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
183 val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
185 // There are two client services, synnefo and pithos.
186 val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
187 val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
188 val Pithos = ClientSim("pithos" )(TheUIDGenerator)
190 val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
191 val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate
193 val BillingMonthInfoJan = {
194 val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
195 BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
197 val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1))
198 val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1))
200 // Store the default policy
201 val policyDateCalc = StartOfBillingYearDateCalc.copy
202 val policyOccurredMillis = policyDateCalc.toMillis
203 val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
204 val policyValidToMillis = policyDateCalc.copy.goNextYear.toMillis
206 aquarium.policyStore.insertPolicy(DefaultPolicy)
208 val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim), aquarium.resourceEventStore)
209 val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
211 val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate)
213 // val InitialUserState = UserState.createInitialUserState(
214 // userID = UserCKKL.userID,
215 // userCreationMillis = UserCreationDate.getTime,
216 // totalCredits = 0.0,
217 // initialRole = "default",
218 // initialAgreement = DSLAgreement.DefaultAgreementName
221 val UserStateBootstrapper = UserStateBootstrap(
222 userID = UserCKKL.userID,
223 userCreationMillis = UserCreationDate.getTime(),
224 initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef),
229 // - synnefo is for VMTime and Bandwidth
230 // - pithos is for Diskspace
231 val VMTimeInstanceSim = VMTimeResourceSim.newInstance ("VM.1", UserCKKL, Synnefo)
232 val DiskInstanceSim = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
235 def showUserState(clog: ContextualLogger, workingUserState: WorkingUserState) {
236 // val id = workingUserState.id
237 // val parentId = workingUserState.parentUserStateIDInStore
238 // val credits = workingUserState.totalCredits
239 // val newWalletEntries = workingUserState.newWalletEntries.map(_.toDebugString)
240 // val changeReason = workingUserState.lastChangeReason
241 // val implicitlyIssued = workingUserState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
242 // val latestResourceEvents = workingUserState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
244 // clog.debug("_id = %s", id)
245 // clog.debug("parentId = %s", parentId)
246 // clog.debug("credits = %s", credits)
247 // clog.debug("changeReason = %s", changeReason)
248 // clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
249 // clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
250 // clog.debugSeq("newWalletEntries", newWalletEntries, 0)
254 def showResourceEvents(clog: ContextualLogger): Unit = {
256 clog.begin("Events by OccurredMillis")
258 for(event <- UserCKKL.myResourceEventsByOccurredDate) {
259 clog.debug(event.toDebugString)
262 clog.end("Events by OccurredMillis")
267 def doFullMonthlyBilling(
268 clog: ContextualLogger,
269 billingMonthInfo: BillingMonthInfo,
270 billingTimeMillis: Long) = {
272 ChargingService.replayMonthChargingUpTo(
275 UserStateBootstrapper,
277 MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo),
278 aquarium.userStateStore.insertUserState,
285 clog: ContextualLogger,
286 creditsConsumed: Double,
287 workingUserState: WorkingUserState,
288 accuracy: Double = 0.001
291 val computed = workingUserState.totalCredits
292 Assert.assertEquals(-creditsConsumed, computed, accuracy)
294 clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
298 def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
301 def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
304 * Test a sequence of ON, OFF vmtime events.
308 def testFullOnOff: Unit = {
309 val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
312 ResourceEventStore.clearResourceEvents()
313 val OnOffDurationHrs = 10
314 val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
316 VMTimeInstanceSim.newONOFF(
317 new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
321 val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
323 showResourceEvents(clog)
325 val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
327 showUserState(clog, workingUserState)
329 expectCredits(clog, credits, workingUserState)
336 def testLonelyON: Unit = {
337 val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
340 ResourceEventStore.clearResourceEvents()
342 val JanStart = new MutableDateCalc(2012, 01, 01)
343 val JanEnd = JanStart.copy.goEndOfThisMonth
344 val JanStartDate = JanStart.toDate
345 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
346 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
348 VMTimeInstanceSim.newON(JanStartDate)
350 val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
352 showResourceEvents(clog)
354 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
356 showUserState(clog, userState)
358 expectCredits(clog, credits, userState)
365 def testOrphanOFF: Unit = {
366 val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
369 ResourceEventStore.clearResourceEvents()
371 val JanStart = new MutableDateCalc(2012, 01, 01)
372 val JanEnd = JanStart.copy.goEndOfThisMonth
373 val JanStartDate = JanStart.toDate
374 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
375 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
377 VMTimeInstanceSim.newOFF(JanStartDate)
379 // This is an orphan event, so no credits will be charged
382 showResourceEvents(clog)
384 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
386 showUserState(clog, userState)
388 expectCredits(clog, credits, userState)
395 def testOne: Unit = {
396 val clog = ContextualLogger.fromOther(None, logger, "testOne()")
399 // Let's create our dates of interest
400 val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
401 val VMStartDate = VMStartDateCalc.toDate
403 // Within January, create one VM ON-OFF ...
404 VMTimeInstanceSim.newONOFF(VMStartDate, 9)
406 val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
407 val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
408 val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
409 val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
411 // ... and two diskspace changes
412 DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
413 DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
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)