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.computation.state.UserState
48 import gr.grnet.aquarium.charging._
49 import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy, PolicyModel}
50 import gr.grnet.aquarium.Timespan
51 import gr.grnet.aquarium.computation.state.UserStateBootstrap
52 import gr.grnet.aquarium.charging.reason.{NoSpecificChargingReason, MonthlyBillChargingReason}
53 import gr.grnet.aquarium.charging.state.WorkingUserState
58 * @author Christos KK Loverdos <loverdos@gmail.com>
60 class UserStateComputationsTest extends Loggable {
61 final val DoubleDelta = 0.001
63 final val BandwidthUnitPrice = 3.3 //
64 final val VMTimeUnitPrice = 1.5 //
65 final val DiskspaceUnitPrice = 2.7 //
67 final val OnOffUnitPrice = VMTimeUnitPrice
68 final val ContinuousUnitPrice = DiskspaceUnitPrice
69 final val DiscreteUnitPrice = BandwidthUnitPrice
71 final val DefaultPolicy: PolicyModel = StdPolicy(
74 validityTimespan = Timespan(0),
76 ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
77 ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
78 ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
80 chargingBehaviors = Set(
81 classOf[DiscreteChargingBehavior].getName,
82 classOf[OnOffChargingBehavior].getName,
83 classOf[ContinuousChargingBehavior].getName,
84 classOf[OnceChargingBehavior].getName
87 "default" -> FullPriceTable(Map(
88 "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(BandwidthUnitPrice, None) :: Nil),
89 "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(VMTimeUnitPrice, None) :: Nil),
90 "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(DiskspaceUnitPrice, None) :: Nil)
95 val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel).
96 update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
99 val ResourceEventStore = aquarium.resourceEventStore
101 val ChargingService = aquarium.chargingService
103 val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm {
104 def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) =
105 hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice
107 final val creditsForDiskspace = creditsForContinuous(_, _)
109 def creditsForDiscrete(currentValue: Double) =
110 currentValue * DiscreteUnitPrice
112 final val creditsForBandwidth = creditsForDiscrete(_)
114 def creditsForOnOff(timeDelta: Double) =
115 hrs(timeDelta) * OnOffUnitPrice
117 final val creditsForVMTime = creditsForOnOff(_)
119 @inline private[this]
120 def hrs(millis: Double) = millis / 1000 / 60 / 60
122 def apply(vars: Map[ChargingInput, Any]): Double = {
123 vars.apply(ChargingBehaviorNameInput) match {
124 case ChargingBehaviorAliases.continuous ⇒
125 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
126 val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
127 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
129 Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta)
131 creditsForContinuous(timeDelta, oldTotalAmount)
133 case ChargingBehaviorAliases.discrete ⇒
134 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
135 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
137 Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta)
139 creditsForDiscrete(currentValue)
141 case ChargingBehaviorAliases.onoff ⇒
142 val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
143 val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
145 Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta)
147 creditsForOnOff(timeDelta)
149 case ChargingBehaviorAliases.once ⇒
150 val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
154 throw new AquariumException("Unknown cost policy %s".format(name))
158 override def toString = "DefaultAlgorithm(%s)".format(
160 ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
161 ChargingBehaviorAliases.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
162 ChargingBehaviorAliases.onoff -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
163 ChargingBehaviorAliases.once -> "currentValue"))
166 val DefaultCompiler = new CostPolicyAlgorithmCompiler {
167 def compile(definition: String): ExecutableChargingBehaviorAlgorithm = {
171 //val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is
173 val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime")
175 // For this to work, the definitions must match those in the YAML above.
176 // Those StdXXXResourceSim are just for debugging convenience anyway, so they must match by design.
177 val VMTimeResourceSim = StdVMTimeResourceSim.fromPolicy(DefaultPolicy)
178 val DiskspaceResourceSim = StdDiskspaceResourceSim.fromPolicy(DefaultPolicy)
179 val BandwidthResourceSim = StdBandwidthResourceSim.fromPolicy(DefaultPolicy)
181 // There are two client services, synnefo and pithos.
182 val TheUIDGenerator: UIDGenerator[_] = new ConcurrentVMLocalUIDGenerator
183 val Synnefo = ClientSim("synnefo")(TheUIDGenerator)
184 val Pithos = ClientSim("pithos" )(TheUIDGenerator)
186 val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1)
187 val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate
189 val BillingMonthInfoJan = {
190 val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1)
191 BillingMonthInfo.fromDateCalc(MutableDateCalcJan)
193 val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1))
194 val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1))
196 // Store the default policy
197 val policyDateCalc = StartOfBillingYearDateCalc.copy
198 val policyOccurredMillis = policyDateCalc.toMillis
199 val policyValidFromMillis = policyDateCalc.copy.goPreviousYear.toMillis
200 val policyValidToMillis = policyDateCalc.copy.goNextYear.toMillis
202 aquarium.policyStore.insertPolicy(DefaultPolicy)
204 val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore)
205 val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap
207 val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate)
209 // val InitialUserState = UserState.createInitialUserState(
210 // userID = UserCKKL.userID,
211 // userCreationMillis = UserCreationDate.getTime,
212 // totalCredits = 0.0,
213 // initialRole = "default",
214 // initialAgreement = DSLAgreement.DefaultAgreementName
217 val UserStateBootstrapper = UserStateBootstrap(
218 userID = UserCKKL.userID,
219 userCreationMillis = UserCreationDate.getTime(),
220 initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef),
225 // - synnefo is for VMTime and Bandwidth
226 // - pithos is for Diskspace
227 val VMTimeInstanceSim = VMTimeResourceSim.newInstance ("VM.1", UserCKKL, Synnefo)
228 val BandwidthInstanceSim = BandwidthResourceSim.newInstance("3G.1", UserCKKL, Synnefo)
229 val DiskInstanceSim = DiskspaceResourceSim.newInstance("DISK.1", UserCKKL, Pithos)
232 def showUserState(clog: ContextualLogger, workingUserState: WorkingUserState) {
233 // val id = workingUserState.id
234 // val parentId = workingUserState.parentUserStateIDInStore
235 // val credits = workingUserState.totalCredits
236 // val newWalletEntries = workingUserState.newWalletEntries.map(_.toDebugString)
237 // val changeReason = workingUserState.lastChangeReason
238 // val implicitlyIssued = workingUserState.implicitlyIssuedSnapshot.implicitlyIssuedEvents.map(_.toDebugString)
239 // val latestResourceEvents = workingUserState.latestResourceEventsSnapshot.resourceEvents.map(_.toDebugString)
241 // clog.debug("_id = %s", id)
242 // clog.debug("parentId = %s", parentId)
243 // clog.debug("credits = %s", credits)
244 // clog.debug("changeReason = %s", changeReason)
245 // clog.debugSeq("implicitlyIssued", implicitlyIssued, 0)
246 // clog.debugSeq("latestResourceEvents", latestResourceEvents, 0)
247 // clog.debugSeq("newWalletEntries", newWalletEntries, 0)
251 def showResourceEvents(clog: ContextualLogger): Unit = {
253 clog.begin("Events by OccurredMillis")
255 for(event <- UserCKKL.myResourceEventsByOccurredDate) {
256 clog.debug(event.toDebugString)
259 clog.end("Events by OccurredMillis")
264 def doFullMonthlyBilling(
265 clog: ContextualLogger,
266 billingMonthInfo: BillingMonthInfo,
267 billingTimeMillis: Long) = {
269 ChargingService.replayMonthChargingUpTo(
272 UserStateBootstrapper,
274 MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo),
275 aquarium.userStateStore.insertUserState,
282 clog: ContextualLogger,
283 creditsConsumed: Double,
284 workingUserState: WorkingUserState,
285 accuracy: Double = 0.001
288 val computed = workingUserState.totalCredits
289 Assert.assertEquals(-creditsConsumed, computed, accuracy)
291 clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy)
295 def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60
298 def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong
301 * Test a sequence of ON, OFF vmtime events.
305 def testFullOnOff: Unit = {
306 val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()")
309 ResourceEventStore.clearResourceEvents()
310 val OnOffDurationHrs = 10
311 val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble)
313 VMTimeInstanceSim.newONOFF(
314 new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000
318 val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis)
320 showResourceEvents(clog)
322 val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
324 showUserState(clog, workingUserState)
326 expectCredits(clog, credits, workingUserState)
333 def testLonelyON: Unit = {
334 val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()")
337 ResourceEventStore.clearResourceEvents()
339 val JanStart = new MutableDateCalc(2012, 01, 01)
340 val JanEnd = JanStart.copy.goEndOfThisMonth
341 val JanStartDate = JanStart.toDate
342 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
343 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
345 VMTimeInstanceSim.newON(JanStartDate)
347 val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis)
349 showResourceEvents(clog)
351 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
353 showUserState(clog, userState)
355 expectCredits(clog, credits, userState)
362 def testOrphanOFF: Unit = {
363 val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()")
366 ResourceEventStore.clearResourceEvents()
368 val JanStart = new MutableDateCalc(2012, 01, 01)
369 val JanEnd = JanStart.copy.goEndOfThisMonth
370 val JanStartDate = JanStart.toDate
371 val OnOffImplicitDurationMillis = JanEnd.toMillis - JanStart.toMillis
372 val OnOffImplicitDurationHrs = millis2hrs(OnOffImplicitDurationMillis)
374 VMTimeInstanceSim.newOFF(JanStartDate)
376 // This is an orphan event, so no credits will be charged
379 showResourceEvents(clog)
381 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
383 showUserState(clog, userState)
385 expectCredits(clog, credits, userState)
392 def testOne: Unit = {
393 val clog = ContextualLogger.fromOther(None, logger, "testOne()")
396 // Let's create our dates of interest
397 val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1)
398 val VMStartDate = VMStartDateCalc.toDate
400 // Within January, create one VM ON-OFF ...
401 VMTimeInstanceSim.newONOFF(VMStartDate, 9)
403 val diskConsumptionDateCalc = StartOfBillingYearDateCalc.copy.goPlusHours(3)
404 val diskConsumptionDate1 = diskConsumptionDateCalc.toDate
405 val diskConsumptionDateCalc2 = diskConsumptionDateCalc.copy.goPlusDays(1).goPlusHours(1)
406 val diskConsumptionDate2 = diskConsumptionDateCalc2.toDate
408 // ... and two diskspace changes
409 DiskInstanceSim.consumeMB(diskConsumptionDate1, 99)
410 DiskInstanceSim.consumeMB(diskConsumptionDate2, 23)
412 // 100MB 3G bandwidth
413 val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1)
414 BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0)
416 // ... and one "future" event
417 DiskInstanceSim.consumeMB(
418 StartOfBillingYearDateCalc.copy.
419 goNextMonth.goPlusDays(6).
423 goPlusMillis(7).toDate,
426 showResourceEvents(clog)
428 // Policy: from 2012-01-01 to Infinity
430 clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1)
432 val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis)
434 showUserState(clog, userState)