root / src / test / scala / gr / grnet / aquarium / user / UserStateComputationsTest.scala @ 7dbaeb04
History | View | Annotate | Download (16 kB)
1 |
/* |
---|---|
2 |
* Copyright 2011-2012 GRNET S.A. All rights reserved. |
3 |
* |
4 |
* Redistribution and use in source and binary forms, with or |
5 |
* without modification, are permitted provided that the following |
6 |
* conditions are met: |
7 |
* |
8 |
* 1. Redistributions of source code must retain the above |
9 |
* copyright notice, this list of conditions and the following |
10 |
* disclaimer. |
11 |
* |
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. |
16 |
* |
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. |
29 |
* |
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. |
34 |
*/ |
35 |
|
36 |
package gr.grnet.aquarium.user |
37 |
|
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 |
54 |
|
55 |
|
56 |
/** |
57 |
* |
58 |
* @author Christos KK Loverdos <loverdos@gmail.com> |
59 |
*/ |
60 |
class UserStateComputationsTest extends Loggable { |
61 |
final val DoubleDelta = 0.001 |
62 |
|
63 |
final val BandwidthUnitPrice = 3.3 // |
64 |
final val VMTimeUnitPrice = 1.5 // |
65 |
final val DiskspaceUnitPrice = 2.7 // |
66 |
|
67 |
final val OnOffUnitPrice = VMTimeUnitPrice |
68 |
final val ContinuousUnitPrice = DiskspaceUnitPrice |
69 |
final val DiscreteUnitPrice = BandwidthUnitPrice |
70 |
|
71 |
final val DefaultPolicy: PolicyModel = StdPolicy( |
72 |
id = "policy-1", |
73 |
parentID = None, |
74 |
validityTimespan = Timespan(0), |
75 |
resourceTypes = Set( |
76 |
ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName), |
77 |
ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName), |
78 |
ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName) |
79 |
), |
80 |
chargingBehaviors = Set( |
81 |
classOf[DiscreteChargingBehavior].getName, |
82 |
classOf[OnOffChargingBehavior].getName, |
83 |
classOf[ContinuousChargingBehavior].getName, |
84 |
classOf[OnceChargingBehavior].getName |
85 |
), |
86 |
roleMapping = Map( |
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) |
91 |
)) |
92 |
) |
93 |
) |
94 |
|
95 |
val aquarium = new AquariumBuilder(ResourceLocator.AquariumProperties, ResourceLocator.DefaultPolicyModel). |
96 |
update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider). |
97 |
build() |
98 |
|
99 |
val ResourceEventStore = aquarium.resourceEventStore |
100 |
|
101 |
val ChargingService = aquarium.chargingService |
102 |
|
103 |
val DefaultAlgorithm = new ExecutableChargingBehaviorAlgorithm { |
104 |
def creditsForContinuous(timeDelta: Double, oldTotalAmount: Double) = |
105 |
hrs(timeDelta) * oldTotalAmount * ContinuousUnitPrice |
106 |
|
107 |
final val creditsForDiskspace = creditsForContinuous(_, _) |
108 |
|
109 |
def creditsForDiscrete(currentValue: Double) = |
110 |
currentValue * DiscreteUnitPrice |
111 |
|
112 |
final val creditsForBandwidth = creditsForDiscrete(_) |
113 |
|
114 |
def creditsForOnOff(timeDelta: Double) = |
115 |
hrs(timeDelta) * OnOffUnitPrice |
116 |
|
117 |
final val creditsForVMTime = creditsForOnOff(_) |
118 |
|
119 |
@inline private[this] |
120 |
def hrs(millis: Double) = millis / 1000 / 60 / 60 |
121 |
|
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] |
128 |
|
129 |
Assert.assertEquals(ContinuousUnitPrice, unitPrice, DoubleDelta) |
130 |
|
131 |
creditsForContinuous(timeDelta, oldTotalAmount) |
132 |
|
133 |
case ChargingBehaviorAliases.discrete ⇒ |
134 |
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double] |
135 |
val currentValue = vars(CurrentValueInput).asInstanceOf[Double] |
136 |
|
137 |
Assert.assertEquals(DiscreteUnitPrice, unitPrice, DoubleDelta) |
138 |
|
139 |
creditsForDiscrete(currentValue) |
140 |
|
141 |
case ChargingBehaviorAliases.onoff ⇒ |
142 |
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double] |
143 |
val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double] |
144 |
|
145 |
Assert.assertEquals(OnOffUnitPrice, unitPrice, DoubleDelta) |
146 |
|
147 |
creditsForOnOff(timeDelta) |
148 |
|
149 |
case ChargingBehaviorAliases.once ⇒ |
150 |
val currentValue = vars(CurrentValueInput).asInstanceOf[Double] |
151 |
currentValue |
152 |
|
153 |
case name ⇒ |
154 |
throw new AquariumException("Unknown cost policy %s".format(name)) |
155 |
} |
156 |
} |
157 |
|
158 |
override def toString = "DefaultAlgorithm(%s)".format( |
159 |
Map( |
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")) |
164 |
} |
165 |
|
166 |
val DefaultCompiler = new CostPolicyAlgorithmCompiler { |
167 |
def compile(definition: String): ExecutableChargingBehaviorAlgorithm = { |
168 |
DefaultAlgorithm |
169 |
} |
170 |
} |
171 |
//val DefaultAlgorithm = justForSure(DefaultCompiler.compile("")).get // hardcoded since we know exactly what this is |
172 |
|
173 |
val VMTimeDSLResource = DefaultPolicy.resourceTypesMap("vmtime") |
174 |
|
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) |
180 |
|
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) |
185 |
|
186 |
val StartOfBillingYearDateCalc = new MutableDateCalc(2012, 1, 1) |
187 |
val UserCreationDate = new MutableDateCalc(2011, 11, 1).toDate |
188 |
|
189 |
val BillingMonthInfoJan = { |
190 |
val MutableDateCalcJan = new MutableDateCalc(2012, 1, 1) |
191 |
BillingMonthInfo.fromDateCalc(MutableDateCalcJan) |
192 |
} |
193 |
val BillingMonthInfoFeb = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 2, 1)) |
194 |
val BillingMonthInfoMar = BillingMonthInfo.fromDateCalc(new MutableDateCalc(2012, 3, 1)) |
195 |
|
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 |
201 |
|
202 |
aquarium.policyStore.insertPolicy(DefaultPolicy) |
203 |
|
204 |
val AquariumSim_ = AquariumSim(List(VMTimeResourceSim, DiskspaceResourceSim, BandwidthResourceSim), aquarium.resourceEventStore) |
205 |
val DefaultResourcesMap = DefaultPolicy.resourceTypesMap//AquariumSim_.resourcesMap |
206 |
|
207 |
val UserCKKL = AquariumSim_.newUser("CKKL", UserCreationDate) |
208 |
|
209 |
// val InitialUserState = UserState.createInitialUserState( |
210 |
// userID = UserCKKL.userID, |
211 |
// userCreationMillis = UserCreationDate.getTime, |
212 |
// totalCredits = 0.0, |
213 |
// initialRole = "default", |
214 |
// initialAgreement = DSLAgreement.DefaultAgreementName |
215 |
// ) |
216 |
|
217 |
val UserStateBootstrapper = UserStateBootstrap( |
218 |
userID = UserCKKL.userID, |
219 |
userCreationMillis = UserCreationDate.getTime(), |
220 |
initialAgreement = StdUserAgreement("", None, 0, Long.MaxValue, "default", PolicyDefinedFullPriceTableRef), |
221 |
initialCredits = 0.0 |
222 |
) |
223 |
|
224 |
// By convention |
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) |
230 |
|
231 |
private[this] |
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) |
240 |
// |
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) |
248 |
} |
249 |
|
250 |
private[this] |
251 |
def showResourceEvents(clog: ContextualLogger): Unit = { |
252 |
clog.debug("") |
253 |
clog.begin("Events by OccurredMillis") |
254 |
clog.withIndent { |
255 |
for(event <- UserCKKL.myResourceEventsByOccurredDate) { |
256 |
clog.debug(event.toDebugString) |
257 |
} |
258 |
} |
259 |
clog.end("Events by OccurredMillis") |
260 |
clog.debug("") |
261 |
} |
262 |
|
263 |
private[this] |
264 |
def doFullMonthlyBilling( |
265 |
clog: ContextualLogger, |
266 |
billingMonthInfo: BillingMonthInfo, |
267 |
billingTimeMillis: Long) = { |
268 |
|
269 |
ChargingService.replayMonthChargingUpTo( |
270 |
billingMonthInfo, |
271 |
billingTimeMillis, |
272 |
UserStateBootstrapper, |
273 |
DefaultResourcesMap, |
274 |
MonthlyBillChargingReason(NoSpecificChargingReason(), billingMonthInfo), |
275 |
aquarium.userStateStore.insertUserState, |
276 |
Some(clog) |
277 |
) |
278 |
} |
279 |
|
280 |
private[this] |
281 |
def expectCredits( |
282 |
clog: ContextualLogger, |
283 |
creditsConsumed: Double, |
284 |
workingUserState: WorkingUserState, |
285 |
accuracy: Double = 0.001 |
286 |
): Unit = { |
287 |
|
288 |
val computed = workingUserState.totalCredits |
289 |
Assert.assertEquals(-creditsConsumed, computed, accuracy) |
290 |
|
291 |
clog.info("Consumed %.3f credits [accuracy = %f]", creditsConsumed, accuracy) |
292 |
} |
293 |
|
294 |
private[this] |
295 |
def millis2hrs(millis: Long) = millis.toDouble / 1000 / 60 / 60 |
296 |
|
297 |
private[this] |
298 |
def hrs2millis(hrs: Double) = (hrs * 60 * 60 * 1000).toLong |
299 |
|
300 |
/** |
301 |
* Test a sequence of ON, OFF vmtime events. |
302 |
*/ |
303 |
@Ignore |
304 |
@Test |
305 |
def testFullOnOff: Unit = { |
306 |
val clog = ContextualLogger.fromOther(None, logger, "testFullOnOff()") |
307 |
clog.begin() |
308 |
|
309 |
ResourceEventStore.clearResourceEvents() |
310 |
val OnOffDurationHrs = 10 |
311 |
val OnOffDurationMillis = hrs2millis(OnOffDurationHrs.toDouble) |
312 |
|
313 |
VMTimeInstanceSim.newONOFF( |
314 |
new MutableDateCalc(2012, 01, 10).goPlusHours(13).goPlusMinutes(30).toDate, // 2012-01-10 13:30:00.000 |
315 |
OnOffDurationHrs |
316 |
) |
317 |
|
318 |
val credits = DefaultAlgorithm.creditsForVMTime(OnOffDurationMillis) |
319 |
|
320 |
showResourceEvents(clog) |
321 |
|
322 |
val workingUserState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis) |
323 |
|
324 |
showUserState(clog, workingUserState) |
325 |
|
326 |
expectCredits(clog, credits, workingUserState) |
327 |
|
328 |
clog.end() |
329 |
} |
330 |
|
331 |
@Ignore |
332 |
@Test |
333 |
def testLonelyON: Unit = { |
334 |
val clog = ContextualLogger.fromOther(None, logger, "testLonelyON()") |
335 |
clog.begin() |
336 |
|
337 |
ResourceEventStore.clearResourceEvents() |
338 |
|
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) |
344 |
|
345 |
VMTimeInstanceSim.newON(JanStartDate) |
346 |
|
347 |
val credits = DefaultAlgorithm.creditsForVMTime(OnOffImplicitDurationMillis) |
348 |
|
349 |
showResourceEvents(clog) |
350 |
|
351 |
val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis) |
352 |
|
353 |
showUserState(clog, userState) |
354 |
|
355 |
expectCredits(clog, credits, userState) |
356 |
|
357 |
clog.end() |
358 |
} |
359 |
|
360 |
// @Ignore |
361 |
@Test |
362 |
def testOrphanOFF: Unit = { |
363 |
val clog = ContextualLogger.fromOther(None, logger, "testOrphanOFF()") |
364 |
clog.begin() |
365 |
|
366 |
ResourceEventStore.clearResourceEvents() |
367 |
|
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) |
373 |
|
374 |
VMTimeInstanceSim.newOFF(JanStartDate) |
375 |
|
376 |
// This is an orphan event, so no credits will be charged |
377 |
val credits = 0 |
378 |
|
379 |
showResourceEvents(clog) |
380 |
|
381 |
val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis) |
382 |
|
383 |
showUserState(clog, userState) |
384 |
|
385 |
expectCredits(clog, credits, userState) |
386 |
|
387 |
clog.end() |
388 |
} |
389 |
|
390 |
@Ignore |
391 |
@Test |
392 |
def testOne: Unit = { |
393 |
val clog = ContextualLogger.fromOther(None, logger, "testOne()") |
394 |
clog.begin() |
395 |
|
396 |
// Let's create our dates of interest |
397 |
val VMStartDateCalc = StartOfBillingYearDateCalc.copy.goPlusDays(1).goPlusHours(1) |
398 |
val VMStartDate = VMStartDateCalc.toDate |
399 |
|
400 |
// Within January, create one VM ON-OFF ... |
401 |
VMTimeInstanceSim.newONOFF(VMStartDate, 9) |
402 |
|
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 |
407 |
|
408 |
// ... and two diskspace changes |
409 |
DiskInstanceSim.consumeMB(diskConsumptionDate1, 99) |
410 |
DiskInstanceSim.consumeMB(diskConsumptionDate2, 23) |
411 |
|
412 |
// 100MB 3G bandwidth |
413 |
val bwDateCalc = diskConsumptionDateCalc2.copy.goPlusDays(1) |
414 |
BandwidthInstanceSim.useBandwidth(bwDateCalc.toDate, 100.0) |
415 |
|
416 |
// ... and one "future" event |
417 |
DiskInstanceSim.consumeMB( |
418 |
StartOfBillingYearDateCalc.copy. |
419 |
goNextMonth.goPlusDays(6). |
420 |
goPlusHours(7). |
421 |
goPlusMinutes(7). |
422 |
goPlusSeconds(7). |
423 |
goPlusMillis(7).toDate, |
424 |
777) |
425 |
|
426 |
showResourceEvents(clog) |
427 |
|
428 |
// Policy: from 2012-01-01 to Infinity |
429 |
|
430 |
clog.debugMap("DefaultResourcesMap", DefaultResourcesMap, 1) |
431 |
|
432 |
val userState = doFullMonthlyBilling(clog, BillingMonthInfoJan, BillingMonthInfoJan.monthStopMillis) |
433 |
|
434 |
showUserState(clog, userState) |
435 |
|
436 |
clog.end() |
437 |
} |
438 |
} |