Revision 10c87819
b/src/main/scala/gr/grnet/aquarium/Aquarium.scala | ||
---|---|---|
290 | 290 |
|
291 | 291 |
def initialUserBalance(role: String, referenceTimeMillis: Long): Double = { |
292 | 292 |
// FIXME: Where is the mapping? |
293 |
1000.0
|
|
293 |
0.0 |
|
294 | 294 |
} |
295 | 295 |
|
296 | 296 |
def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = { |
b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala | ||
---|---|---|
312 | 312 |
/* Convert astakos message for adding credits |
313 | 313 |
to a regular RESOURCE message */ |
314 | 314 |
def onHandleAddCreditsEvent(imEvent : IMEventModel) = { |
315 |
DEBUG("Got %s", imEvent.toJsonString) |
|
316 |
|
|
315 | 317 |
val credits = imEvent.details(IMEventModel.DetailsNames.credits).toInt.toDouble |
316 | 318 |
val event = new StdResourceEvent( |
317 | 319 |
imEvent.id, |
... | ... | |
325 | 327 |
imEvent.eventVersion, |
326 | 328 |
imEvent.details |
327 | 329 |
) |
328 |
//Console.err.println("Event: " + event) |
|
329 |
//Console.err.println("Total credits before: " + _workingUserState.totalCredits) |
|
330 |
DEBUG("Transformed to %s", event) |
|
331 |
DEBUG("Total credits before: %s", _workingUserState.totalCredits) |
|
332 |
aquarium.resourceEventStore.insertResourceEvent(event) |
|
330 | 333 |
onProcessResourceEvent(new ProcessResourceEvent(event)) |
331 |
//Console.err.println("Total credits after: " + _workingUserState.totalCredits)
|
|
334 |
DEBUG("Total credits after: %s", _workingUserState.totalCredits)
|
|
332 | 335 |
//Console.err.println("OK.") |
333 | 336 |
} |
334 | 337 |
|
b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala | ||
---|---|---|
79 | 79 |
|
80 | 80 |
/** |
81 | 81 |
* |
82 |
* @return The number of wallet entries recorded and the new total credits |
|
82 |
* @return The number of wallet entries recorded and the credit difference generated during processing (these are |
|
83 |
* the credits to subtract from the total credits). |
|
83 | 84 |
*/ |
84 | 85 |
def processResourceEvent( |
85 | 86 |
aquarium: Aquarium, |
b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala | ||
---|---|---|
35 | 35 |
|
36 | 36 |
package gr.grnet.aquarium.charging |
37 | 37 |
|
38 |
import scala.collection.immutable |
|
39 |
import scala.collection.mutable |
|
40 |
|
|
41 |
import gr.grnet.aquarium.event.model.resource.ResourceEventModel |
|
42 |
import gr.grnet.aquarium.{Aquarium, AquariumInternalError} |
|
43 |
import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType} |
|
44 |
import gr.grnet.aquarium.util._ |
|
45 |
import gr.grnet.aquarium.util.date.TimeHelpers |
|
38 |
import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel} |
|
46 | 39 |
import gr.grnet.aquarium.charging.wallet.WalletEntry |
47 | 40 |
import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo} |
48 |
import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
|
|
41 |
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
|
|
49 | 42 |
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot |
43 |
import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType} |
|
50 | 44 |
import gr.grnet.aquarium.store.PolicyStore |
45 |
import gr.grnet.aquarium.util._ |
|
46 |
import gr.grnet.aquarium.util.date.TimeHelpers |
|
47 |
import gr.grnet.aquarium.{Aquarium, AquariumInternalError} |
|
48 |
import scala.collection.immutable |
|
49 |
import scala.collection.mutable |
|
50 |
|
|
51 | 51 |
|
52 | 52 |
/** |
53 | 53 |
* A charging behavior indicates how charging for a resource will be done |
... | ... | |
82 | 82 |
) |
83 | 83 |
} |
84 | 84 |
|
85 |
final protected def ensureWorkingState( |
|
85 |
final protected def ensureInitializedWorkingState(
|
|
86 | 86 |
workingResourcesChargingState: WorkingResourcesChargingState, |
87 | 87 |
resourceEvent: ResourceEventModel |
88 | 88 |
) { |
89 |
ensureResourcesChargingStateDetails(workingResourcesChargingState.details) |
|
90 |
ensureResourceInstanceChargingState(workingResourcesChargingState, resourceEvent) |
|
89 |
ensureInitializedResourcesChargingStateDetails(workingResourcesChargingState.details)
|
|
90 |
ensureInitializedResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
|
|
91 | 91 |
} |
92 | 92 |
|
93 |
protected def ensureResourcesChargingStateDetails( |
|
94 |
details: mutable.Map[String, Any] |
|
95 |
) {} |
|
93 |
protected def ensureInitializedResourcesChargingStateDetails(details: mutable.Map[String, Any]) {} |
|
96 | 94 |
|
97 |
protected def ensureResourceInstanceChargingState( |
|
95 |
protected def ensureInitializedResourceInstanceChargingState(
|
|
98 | 96 |
workingResourcesChargingState: WorkingResourcesChargingState, |
99 | 97 |
resourceEvent: ResourceEventModel |
100 | 98 |
) { |
... | ... | |
110 | 108 |
} |
111 | 109 |
} |
112 | 110 |
|
111 |
protected def fillWorkingResourceInstanceChargingStateFromEvent( |
|
112 |
workingResourceInstanceChargingState: WorkingResourceInstanceChargingState, |
|
113 |
resourceEvent: ResourceEventModel |
|
114 |
) { |
|
115 |
|
|
116 |
workingResourceInstanceChargingState.currentValue = resourceEvent.value |
|
117 |
} |
|
118 |
|
|
113 | 119 |
protected def computeWalletEntriesForNewEvent( |
114 | 120 |
resourceEvent: ResourceEventModel, |
115 | 121 |
resourceType: ResourceType, |
... | ... | |
196 | 202 |
|
197 | 203 |
walletEntryRecorder.apply(newWalletEntry) |
198 | 204 |
|
199 |
(1, newTotalCredits)
|
|
205 |
(1, sumOfCreditsToSubtract)
|
|
200 | 206 |
} |
201 | 207 |
|
202 | 208 |
|
... | ... | |
217 | 223 |
totalCredits |
218 | 224 |
) |
219 | 225 |
|
220 |
fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource) |
|
226 |
fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource, logger)
|
|
221 | 227 |
} |
222 | 228 |
|
223 | 229 |
/** |
b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala | ||
---|---|---|
206 | 206 |
} |
207 | 207 |
|
208 | 208 |
val m0 = TimeHelpers.nowMillis() |
209 |
val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent(
|
|
209 |
val (walletEntriesCount, creditsToSubtract) = chargingBehavior.processResourceEvent(
|
|
210 | 210 |
aquarium, |
211 | 211 |
resourceEvent, |
212 | 212 |
resourceType, |
... | ... | |
223 | 223 |
} |
224 | 224 |
|
225 | 225 |
workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis) |
226 |
workingUserState.totalCredits = newTotalCredits
|
|
226 |
workingUserState.totalCredits -= creditsToSubtract
|
|
227 | 227 |
|
228 | 228 |
true |
229 | 229 |
} |
b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala | ||
---|---|---|
35 | 35 |
|
36 | 36 |
package gr.grnet.aquarium.charging |
37 | 37 |
|
38 |
import gr.grnet.aquarium.Aquarium |
|
39 |
import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState} |
|
40 |
import gr.grnet.aquarium.charging.wallet.WalletEntry |
|
41 |
import gr.grnet.aquarium.computation.BillingMonthInfo |
|
38 | 42 |
import gr.grnet.aquarium.event.model.resource.ResourceEventModel |
39 | 43 |
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot |
40 |
import gr.grnet.aquarium.policy.ResourceType
|
|
41 |
import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
|
|
44 |
import gr.grnet.aquarium.policy.{FullPriceTable, ResourceType}
|
|
45 |
import gr.grnet.aquarium.util.LogHelpers.Debug
|
|
42 | 46 |
import scala.collection.mutable |
43 |
import gr.grnet.aquarium.Aquarium |
|
44 |
import gr.grnet.aquarium.computation.BillingMonthInfo |
|
45 |
import gr.grnet.aquarium.charging.wallet.WalletEntry |
|
46 | 47 |
|
47 | 48 |
/** |
48 | 49 |
* In practice a resource usage will be charged for the total amount of usage |
... | ... | |
63 | 64 |
|
64 | 65 |
val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount |
65 | 66 |
val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice |
66 |
val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format( |
|
67 |
val explanation = "Time(%s) * OldTotal(%s) * UnitPrice(%s)".format(
|
|
67 | 68 |
hrs(timeDeltaMillis), |
68 | 69 |
oldAccumulatingAmount, |
69 | 70 |
unitPrice |
... | ... | |
80 | 81 |
referenceTimeslot: Timeslot, |
81 | 82 |
totalCredits: Double |
82 | 83 |
): List[String] = { |
83 |
Nil
|
|
84 |
List(FullPriceTable.DefaultSelectorKey)
|
|
84 | 85 |
} |
85 | 86 |
|
86 | 87 |
def initialChargingDetails: Map[String, Any] = Map() |
... | ... | |
102 | 103 |
|
103 | 104 |
override def processResourceEvent( |
104 | 105 |
aquarium: Aquarium, |
105 |
currentResourceEvent: ResourceEventModel,
|
|
106 |
resourceEvent: ResourceEventModel,
|
|
106 | 107 |
resourceType: ResourceType, |
107 | 108 |
billingMonthInfo: BillingMonthInfo, |
108 | 109 |
workingResourcesChargingState: WorkingResourcesChargingState, |
... | ... | |
110 | 111 |
totalCredits: Double, |
111 | 112 |
walletEntryRecorder: WalletEntry ⇒ Unit |
112 | 113 |
): (Int, Double) = { |
113 |
(0,0) |
|
114 |
|
|
115 |
// 1. Ensure proper initial state per resource and per instance |
|
116 |
ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent) |
|
117 |
|
|
118 |
// 2. Fill in data from the new event |
|
119 |
val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance |
|
120 |
val workingResourcesChargingStateDetails = workingResourcesChargingState.details |
|
121 |
val instanceID = resourceEvent.instanceID |
|
122 |
val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID) |
|
123 |
fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent) |
|
124 |
|
|
125 |
val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match { |
|
126 |
case Some(previousEvent) ⇒ |
|
127 |
Debug(logger, "I have previous event %s", previousEvent.toDebugString) |
|
128 |
previousEvent |
|
129 |
|
|
130 |
|
|
131 |
case None ⇒ |
|
132 |
// We do not have the needed previous event, so this must be the first resource event of its kind, ever. |
|
133 |
// Let's see if we can create a dummy previous event. |
|
134 |
Debug(logger, "First event of its kind %s", resourceEvent.toDebugString) |
|
135 |
|
|
136 |
val dummyFirstEventDetails = Map( |
|
137 |
ResourceEventModel.Names.details_aquarium_is_synthetic -> "true", |
|
138 |
ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true", |
|
139 |
ResourceEventModel.Names.details_aquarium_reference_event_id -> resourceEvent.id, |
|
140 |
ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> resourceEvent.stringIDInStoreOrEmpty |
|
141 |
) |
|
142 |
|
|
143 |
val dummyFirstEventValue = 0.0 // TODO From configuration |
|
144 |
val dummyFirstEvent = resourceEvent.withDetailsAndValue( |
|
145 |
dummyFirstEventDetails, |
|
146 |
dummyFirstEventValue, |
|
147 |
billingMonthInfo.monthStartMillis // TODO max(billingMonthInfo.monthStartMillis, userAgreementModel.validFromMillis) |
|
148 |
) |
|
149 |
|
|
150 |
Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString) |
|
151 |
|
|
152 |
dummyFirstEvent |
|
153 |
} |
|
154 |
|
|
155 |
val retval = computeWalletEntriesForNewEvent( |
|
156 |
resourceEvent, |
|
157 |
resourceType, |
|
158 |
billingMonthInfo, |
|
159 |
totalCredits, |
|
160 |
Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis), |
|
161 |
userAgreements.agreementByTimeslot, |
|
162 |
workingResourcesChargingStateDetails, |
|
163 |
workingResourceInstanceChargingState, |
|
164 |
aquarium.policyStore, |
|
165 |
walletEntryRecorder |
|
166 |
) |
|
167 |
|
|
168 |
// We need just one previous event, so we update it |
|
169 |
workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent) |
|
170 |
|
|
171 |
retval |
|
114 | 172 |
} |
115 | 173 |
} |
116 | 174 |
|
b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala | ||
---|---|---|
60 | 60 |
): (Double /* credits */, String /* explanation */) = { |
61 | 61 |
|
62 | 62 |
val currentValue = workingResourceInstanceChargingState.currentValue |
63 |
val credits = currentValue |
|
64 |
val explanation = "Value(%s)".format(currentValue) |
|
63 |
// Always remember to multiply with the `unitPrice`, since it scales the credits, depending on |
|
64 |
// the particular resource type tha applies. |
|
65 |
val credits = currentValue * unitPrice |
|
66 |
val explanation = "Value(%s) * UnitPrice(%s)".format(currentValue, unitPrice) |
|
65 | 67 |
|
66 | 68 |
(credits, explanation) |
67 | 69 |
} |
... | ... | |
90 | 92 |
// But we cannot just apply them, since we also need to take into account the unit price. |
91 | 93 |
// Normally, the unit price is 1.0 but we have the flexibility to allow more stuff). |
92 | 94 |
|
93 |
val instanceID = resourceEvent.instanceID |
|
94 |
val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance |
|
95 |
|
|
96 |
// 0. Ensure proper state per resource and per instance |
|
97 |
ensureWorkingState(workingResourcesChargingState, resourceEvent) |
|
98 |
|
|
99 |
// 1. Find the unit price at the moment of the event |
|
95 |
// 1. Ensure proper initial state per resource and per instance |
|
96 |
ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent) |
|
100 | 97 |
|
98 |
// 2. Fill in data from the new event |
|
99 |
val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance |
|
101 | 100 |
val workingResourcesChargingStateDetails = workingResourcesChargingState.details |
101 |
val instanceID = resourceEvent.instanceID |
|
102 | 102 |
val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID) |
103 |
fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent) |
|
103 | 104 |
|
104 | 105 |
computeWalletEntriesForNewEvent( |
105 | 106 |
resourceEvent, |
b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala | ||
---|---|---|
60 | 60 |
): (Double /* credits */, String /* explanation */) = { |
61 | 61 |
|
62 | 62 |
val credits = hrs(timeDeltaMillis) * unitPrice |
63 |
val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice) |
|
63 |
val explanation = "Time(%s) * UnitPrice(%s)".format(hrs(timeDeltaMillis), unitPrice)
|
|
64 | 64 |
|
65 | 65 |
(credits, explanation) |
66 | 66 |
|
b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala | ||
---|---|---|
79 | 79 |
this.previousValue = this.currentValue |
80 | 80 |
this.currentValue = value |
81 | 81 |
} |
82 |
|
|
83 |
def setOnePreviousEvent(event: ResourceEventModel) { |
|
84 |
this.previousEvents = event :: Nil |
|
85 |
} |
|
82 | 86 |
} |
b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala | ||
---|---|---|
36 | 36 |
package gr.grnet.aquarium.charging.state |
37 | 37 |
|
38 | 38 |
import scala.collection.mutable |
39 |
import gr.grnet.aquarium.event.model.resource.ResourceEventModel |
|
39 | 40 |
|
40 | 41 |
/** |
41 |
* Working (mutable state) for a resource instances of the same resource type.
|
|
42 |
* Working (mutable state) for resource instances of the same resource type. |
|
42 | 43 |
* |
43 | 44 |
* @param details Generic state related to the type of resource as a whole |
44 | 45 |
* @param stateOfResourceInstance A map from `instanceID` to |
... | ... | |
47 | 48 |
* @author Christos KK Loverdos <loverdos@gmail.com> |
48 | 49 |
*/ |
49 | 50 |
final class WorkingResourcesChargingState( |
50 |
val details: mutable.Map[String, Any], |
|
51 |
val stateOfResourceInstance: mutable.Map[String, WorkingResourceInstanceChargingState] |
|
51 |
val details: mutable.Map[String /* any string */, Any],
|
|
52 |
val stateOfResourceInstance: mutable.Map[String /* InstanceID */, WorkingResourceInstanceChargingState]
|
|
52 | 53 |
) { |
53 | 54 |
|
54 | 55 |
def immutableDetails = Map(this.details.toSeq: _*) |
... | ... | |
64 | 65 |
stateOfResourceInstance = immutableStateOfResourceInstance |
65 | 66 |
) |
66 | 67 |
} |
68 |
|
|
69 |
/** |
|
70 |
* Find the most recent (latest) holder of a resource event. |
|
71 |
*/ |
|
72 |
// def findResourceInstanceOfLatestEvent: Option[WorkingResourceInstanceChargingState] = { |
|
73 |
// stateOfResourceInstance.values.toArray.sortWith { (a, b) ⇒ |
|
74 |
// (a.previousEvents, b.previousEvents |
|
75 |
// } |
|
76 |
// } |
|
67 | 77 |
} |
b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala | ||
---|---|---|
36 | 36 |
package gr.grnet.aquarium.policy |
37 | 37 |
|
38 | 38 |
import gr.grnet.aquarium.AquariumInternalError |
39 |
import scala.annotation.tailrec |
|
40 | 39 |
import gr.grnet.aquarium.util.shortNameOfType |
40 |
import gr.grnet.aquarium.util.LogHelpers.Debug |
|
41 |
import org.slf4j.Logger |
|
42 |
import scala.annotation.tailrec |
|
41 | 43 |
|
42 | 44 |
/** |
43 | 45 |
* A full price table provides detailed pricing information for all resource types. |
... | ... | |
57 | 59 |
|
58 | 60 |
def effectivePriceTableOfSelectorForResource( |
59 | 61 |
selectorPath: List[String], |
60 |
resource: String |
|
62 |
resource: String, |
|
63 |
logger: Logger |
|
61 | 64 |
): EffectivePriceTable = { |
62 | 65 |
|
63 | 66 |
// Most of the code is for exceptional cases, which we identify in detail. |
... | ... | |
67 | 70 |
partialSelectorData: Any |
68 | 71 |
): EffectivePriceTable = { |
69 | 72 |
|
73 |
Debug(logger, "find: ") |
|
74 |
Debug(logger, " partialSelectorPath = %s", partialSelectorPath.mkString("/")) |
|
75 |
Debug(logger, " partialSelectorData = %s", partialSelectorData) |
|
76 |
|
|
70 | 77 |
partialSelectorPath match { |
71 | 78 |
case selector :: Nil ⇒ |
72 | 79 |
// One selector, meaning that the selectorData must be a Map[String, EffectivePriceTable] |
... | ... | |
161 | 168 |
} |
162 | 169 |
|
163 | 170 |
case Nil ⇒ |
164 |
throw new AquariumInternalError("") |
|
171 |
throw new AquariumInternalError( |
|
172 |
"[AQU-SEL-007] No selector path for resource %s".format(resource) |
|
173 |
) |
|
165 | 174 |
|
166 | 175 |
} |
167 | 176 |
} |
168 | 177 |
|
178 |
Debug(logger, "effectivePriceTableOfSelectorForResource:") |
|
179 |
Debug(logger, " selectorPath = %s", selectorPath.mkString("/")) |
|
180 |
|
|
169 | 181 |
val selectorDataOpt = perResource.get(resource) |
170 | 182 |
if(selectorDataOpt.isEmpty) { |
171 | 183 |
throw new AquariumInternalError("Unknown resource type '%s'", resource) |
172 | 184 |
} |
173 | 185 |
val selectorData = selectorDataOpt.get |
174 | 186 |
|
187 |
Debug(logger, " selectorData = %s", selectorData) |
|
175 | 188 |
find(selectorPath, selectorData) |
176 | 189 |
} |
177 | 190 |
} |
Also available in: Unified diff