Statistics
| Branch: | Tag: | Revision:

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
}