Revision 95fe951d

b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala
41 41
import java.util.Date
42 42
import gr.grnet.aquarium.util.{CryptoUtils, Loggable}
43 43
import com.ckkloverdos.maybe.{NoVal, Maybe, Failed, Just}
44
import gr.grnet.aquarium.store.PolicyStore
44 45

  
45 46
/**
46 47
 * Methods for converting accounting events to wallet entries.
47 48
 *
48 49
 * @author Georgios Gousios <gousiosg@gmail.com>
50
 * @author Christos KK Loverdos <loverdos@gmail.com>
49 51
 */
50 52
trait Accounting extends DSLUtils with Loggable {
51 53

  
54
  def computeWalletEntries(userId: String,
55
                           costPolicy: DSLCostPolicy,
56
                           previousResourceEventM: Maybe[ResourceEvent],
57
                           previousAccumulatingAmount: Double,
58
                           currentResourceEvent: ResourceEvent,
59
                           resourcesMap: DSLResourcesMap,
60
                           policyStore: PolicyStore): Maybe[Traversable[WalletEntry]] = Maybe {
61
    val resource   = currentResourceEvent.resource
62
    val instanceId = currentResourceEvent.instanceId
63
    val currentValue = currentResourceEvent.value
64

  
65
    // Validations
66
    // 1. Validate cost policy
67
    val actualCostPolicyM = currentResourceEvent.findCostPolicyM(resourcesMap)
68
    val currentResourceEventDebugStr = currentResourceEvent.toDebugString(resourcesMap, false)
69
    actualCostPolicyM match {
70
      case Just(actualCostPolicy) ⇒
71
        if(costPolicy != actualCostPolicy) {
72
          throw new Exception("Actual cost policy %s is not the same as provided %s for event %s".
73
            format(actualCostPolicy, costPolicy, currentResourceEventDebugStr))
74
        }
75
      case _ ⇒
76
        throw new Exception("Could not verify cost policy %s for event %s".
77
          format(costPolicy, currentResourceEventDebugStr))
78
    }
79
    // 2. Validate previous resource event
80
    previousResourceEventM match {
81
      case Just(previousResourceEvent) ⇒
82
        if(!costPolicy.needsPreviousEventForCreditAndAmountCalculation) {
83
          throw new Exception("Provided previous event but cost policy %s does not need one".format(costPolicy))
84
        }
85
        // 3. resource and instanceId
86
        val previousResource = previousResourceEvent.resource
87
        val previousInstanceId = previousResourceEvent.instanceId
88
        (resource == previousResource, instanceId == previousInstanceId) match {
89
          case (true, true)  ⇒
90

  
91
          case (true, false) ⇒
92
            throw new Exception("Resource instance IDs do not match (%s vs %s) for current and previous event".
93
              format(instanceId, previousInstanceId))
94

  
95
          case (false, true) ⇒
96
            throw new Exception("Resource types do not match (%s vs %s) for current and previous event".
97
              format(resource, previousResource))
98

  
99
          case (false, false) ⇒
100
            throw new Exception("Resource types and instance IDs do not match (%s vs %s) for current and previous event".
101
              format((resource, instanceId), (previousResource, previousInstanceId)))
102
        }
103
      case NoVal ⇒
104
        if(costPolicy.needsPreviousEventForCreditAndAmountCalculation) {
105
          throw new Exception("Did not provid previous event but cost policy %s needa one".format(costPolicy))
106
        }
107
      case failed @ Failed(e, m) ⇒
108
        throw new Exception("Error obtaining previous event".format(m), e)
109
    }
110

  
111
    //
112
    Nil
113
  }
114

  
52 115
  def chargeEvent2( oldResourceEventM: Maybe[ResourceEvent],
53 116
                   newResourceEvent: ResourceEvent,
54 117
                   dslAgreement: DSLAgreement,
b/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLCostPolicy.scala
55 55
  
56 56
  def isNamed(aName: String): Boolean = aName == name
57 57
  
58
  def needsPreviousEventForCreditCalculation: Boolean
58
  def needsPreviousEventForCreditAndAmountCalculation: Boolean
59 59

  
60 60
  /**
61 61
   * True if the resource event has an absolute value.
......
83 83
   */
84 84
  def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
85 85

  
86
  def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double
86
  /**
87
   * Given the old amount of a resource instance (see [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]]) and the
88
   * value arriving in a new resource event, compute the new instance amount.
89
   *
90
   * Note that the `oldAmount` does not make sense for all types of [[gr.grnet.aquarium.logic.accounting.dsl.DSLCostPolicy]],
91
   * in which case it is ignored.
92
   *
93
   * @param oldAmount the old accumulating amount
94
   * @param newEventValue the value contained in a newly arrived [[gr.grnet.aquarium.logic.events.ResourceEvent]]
95
   * @return
96
   */
97
  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double): Double
87 98

  
88 99
  def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double
89 100

  
90 101
  /**
91
   * Th every initial amount.
102
   * The initial amount.
92 103
   */
93 104
  def getResourceInstanceInitialAmount: Double
94 105

  
......
108 119
   * The only exception to the rule is ON events for [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
109 120
   */
110 121
  def isBillableEventBasedOnValue(eventValue: Double): Boolean = true
122
  
123
  def accumulatesAmount: Boolean = false
111 124
}
112 125

  
113 126
object DSLCostPolicyNames {
......
142 155
 * is diskspace.
143 156
 */
144 157
case object ContinuousCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.continuous) {
145
  def needsPreviousEventForCreditCalculation: Boolean = true
158
  def needsPreviousEventForCreditAndAmountCalculation: Boolean = true
146 159

  
147 160
  override def needsAbsValueForCreditCalculation = true
148 161

  
......
159 172
    }
160 173
  }
161 174

  
162
  def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
175
  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double): Double = {
163 176
    oldAmount + newEventValue
164 177
  }
165 178

  
......
192 205
 * cloud application and books in a book lending application.
193 206
 */
194 207
case object OnOffCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.onoff) {
195
  def needsPreviousEventForCreditCalculation: Boolean = true
208
  def needsPreviousEventForCreditAndAmountCalculation: Boolean = true
196 209

  
197 210
  override def needsAbsValueForCreditCalculation = true
198 211

  
......
202 215
    Just(newEventValue)
203 216
  }
204 217

  
205
  def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
218
  /**
219
   *
220
   * @param oldAmount is ignored
221
   * @param newEventValue
222
   * @return
223
   */
224
  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double): Double = {
206 225
    newEventValue
207 226
  }
208 227
  
......
271 290
 * that should be charged per volume once (e.g. the allocation of a volume)
272 291
 */
273 292
case object DiscreteCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.discrete) {
274
  def needsPreviousEventForCreditCalculation: Boolean = false
293
  def needsPreviousEventForCreditAndAmountCalculation: Boolean = false
275 294

  
276 295
  override def needsDiffValueForCreditCalculation = true
277 296

  
......
281 300
    oldAmountM.map(_ + newEventValue)
282 301
  }
283 302

  
284
  def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
303
  def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double): Double = {
285 304
    oldAmount + newEventValue
286 305
  }
287 306

  
b/src/main/scala/gr/grnet/aquarium/user/UserState.scala
39 39
import net.liftweb.json.{JsonAST, Xml}
40 40
import gr.grnet.aquarium.logic.accounting.dsl.DSLAgreement
41 41
import com.ckkloverdos.maybe.{Failed, Maybe}
42
import gr.grnet.aquarium.logic.events.{WalletEntry, ResourceEvent}
43
import java.util.Date
44
import gr.grnet.aquarium.util.date.DateCalculator
42
import gr.grnet.aquarium.logic.events.WalletEntry
45 43

  
46 44

  
47 45
/**
b/src/main/scala/gr/grnet/aquarium/user/UserStateComputations.scala
282 282
          currentResourceEvent <- allResourceEventsForMonth
283 283
        } {
284 284
          _eventCounter = _eventCounter + 1
285
          val theResource = currentResourceEvent.resource
286
          val theInstanceId = currentResourceEvent.instanceId
287
          val theValue = currentResourceEvent.value
285 288

  
286 289
          clog.indent()
287
          clog.debug("Processing [%03d] %s", _eventCounter, rcDebugInfo(currentResourceEvent))
290
          clog.debug("Processing %s", rcDebugInfo(currentResourceEvent))
288 291
          clog.indent()
289 292

  
290
          clog.debug("%s previousResourceEvents = %s", previousResourceEvents.size, previousResourceEvents)
293
          clog.debug("%s previousResourceEvents", previousResourceEvents.size)
291 294
          if(previousResourceEvents.size > 0) {
292 295
            clog.indent()
293 296
            previousResourceEvents.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
294 297
            clog.unindent()
295 298
          }
296
          clog.debug("%s theImplicitOFFs = %s", theImplicitOFFs.size, theImplicitOFFs)
299
          clog.debug("%s theImplicitOFFs", theImplicitOFFs.size)
297 300
          if(theImplicitOFFs.size > 0) {
298 301
            clog.indent()
299 302
            theImplicitOFFs.foreach(ev ⇒ clog.debug("%s", rcDebugInfo(ev)))
300 303
            clog.unindent()
301 304
          }
302 305

  
303
          // Ignore the event if it is not billable (but still record it in the "previous" stuff.
306
          // Ignore the event if it is not billable (but still record it in the "previous" stuff).
304 307
          // But to make this decision, we need the cost policy.
305 308
          val costPolicyM = currentResourceEvent.findCostPolicyM(defaultResourcesMap)
306 309
          costPolicyM match {
310
            // We have a cost policy
307 311
            case Just(costPolicy) ⇒
312
              clog.debug("Cost policy: %s", costPolicy)
308 313
              val isBillable = costPolicy.isBillableEventBasedOnValue(currentResourceEvent.value)
309 314
              isBillable match {
315
                // The resource event is not billable
310 316
                case false ⇒
311 317
                  clog.debug("Ignoring not billable %s", rcDebugInfo(currentResourceEvent))
318

  
319
                // The resource event is billable
312 320
                case true ⇒
321
                  costPolicy.needsPreviousEventForCreditAndAmountCalculation match {
322
                    // We need the previous event to calculate credit & amount
323
                    case true  ⇒
324
                      val previousResourceEventM = findAndRemovePreviousResourceEvent(theResource, theInstanceId)
325

  
326
                      previousResourceEventM match {
327
                        // Found previous event
328
                        case Just(previousResourceEvent) ⇒
329
                          clog.debug("Previous  : %s", previousResourceEvent)
330

  
331
                          // A. Compute new resource instance accumulating amount
332
                          //    But first ask for the current one
333
                          val newAmount = costPolicy.accumulatesAmount match {
334
                            // The amount for this resource is accumulating
335
                            case true  ⇒
336
                              val defaultInstanceAmount = costPolicy.getResourceInstanceInitialAmount
337
                              val currentAmount = _workingUserState.getResourceInstanceAmount(
338
                                theResource,
339
                                theInstanceId,
340
                                defaultInstanceAmount)
341

  
342
                              val newAmount = costPolicy.computeNewAccumulatingAmount(currentAmount, theValue)
343
                              newAmount
344

  
345
                            // The amount for this resource is not accumulating
346
                            case false ⇒
347
                              val newAmount = costPolicy.computeNewAccumulatingAmount(-1.0, theValue)
348
                              newAmount
349
                          }
350

  
351
                          // C. Compute new wallet entries
352
//                          accounting.chargeEvent2()
353
                          // B. Compute new credit amount
354

  
355
                        // Did not find previous event.
356
                        case NoVal ⇒
357
                        // So it must be the first ever, in which case we assume a previous value of zero
358

  
359
                        // Could not find previous event
360
                        case failed@Failed(_, _) ⇒
361
                          clog.error(failed)
362
                      }
363

  
364
                    // We do not need the previous event to calculate credit & amount
365
                    case false ⇒
366
                      clog.debug("No need for previous event")
367
                  }
368
                  
313 369
                  ()
314 370
              }
315 371

  
316
              // We finished processing with this event, now let's make it the latest of its kind
372
              // After processing, all event, billable or not update the previous state
317 373
              previousResourceEvents.updateResourceEvent(currentResourceEvent)
374

  
375
            // We do not have a cost policy
318 376
            case NoVal ⇒
319 377
              // Now, this is a matter of politics: what do we do if no policy was found?
320 378
              clog.error("No cost policy for %s", rcDebugInfo(currentResourceEvent))
379

  
380
            // Could not retrieve cost policy
321 381
            case failed @ Failed(e, m) ⇒
322 382
              clog.error("Error obtaining cost policy for %s", rcDebugInfo(currentResourceEvent))
323 383
              clog.error(e, m)

Also available in: Unified diff