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