2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
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.
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.
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.
36 package gr.grnet.aquarium.computation
38 import collection.immutable.SortedMap
39 import com.ckkloverdos.maybe.{NoVal, Maybe, Just}
40 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
41 import gr.grnet.aquarium.store.PolicyStore
42 import gr.grnet.aquarium.util.date.MutableDateCalc
43 import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
44 import gr.grnet.aquarium.event.model.resource.ResourceEventModel
45 import gr.grnet.aquarium.logic.accounting.algorithm.SimpleExecutableChargingBehaviorAlgorithm
46 import gr.grnet.aquarium.logic.accounting.dsl.{Timeslot}
47 import gr.grnet.aquarium.policy._
48 import collection.immutable
49 import com.ckkloverdos.maybe.Just
50 import gr.grnet.aquarium.computation.Chargeslot
52 import com.ckkloverdos.maybe.Just
53 import gr.grnet.aquarium.computation.Chargeslot
55 import com.ckkloverdos.maybe.Just
56 import gr.grnet.aquarium.policy.ResourceType
57 import gr.grnet.aquarium.policy.EffectiveUnitPrice
58 import gr.grnet.aquarium.computation.Chargeslot
62 * Methods for converting accounting events to wallet entries.
64 * @author Georgios Gousios <gousiosg@gmail.com>
65 * @author Christos KK Loverdos <loverdos@gmail.com>
67 trait TimeslotComputations extends Loggable {
68 // TODO: favour composition over inheritance until we decide what to do with DSLUtils (and TimeslotComputations).
69 //protected val dslUtils = new DSLUtils {}
72 * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
74 * @param referenceTimeslot
75 * @param policyTimeslots
76 * @param agreementTimeslots
80 def splitTimeslotByPoliciesAndAgreements(
81 referenceTimeslot: Timeslot,
82 policyTimeslots: List[Timeslot],
83 agreementTimeslots: List[Timeslot],
84 clogM: Maybe[ContextualLogger] = NoVal
87 // Align policy and agreement validity timeslots to the referenceTimeslot
88 val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
89 val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
91 val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
97 * Given a reference timeslot, we have to break it up to a series of timeslots where a particular
98 * algorithm and price unit is in effect.
102 def resolveEffectiveUnitPrices(
103 alignedTimeslot: Timeslot,
105 agreement: UserAgreementModel,
106 resourceType: ResourceType,
107 clogOpt: Option[ContextualLogger] = None
108 ): SortedMap[Timeslot, Double] = {
110 val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
112 // Note that most of the code is taken from calcChangeChunks()
113 val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType)
114 ret map {case (t,p) => (t,p.unitPrice)}
118 def computeInitialChargeslots(
119 referenceTimeslot: Timeslot,
120 resourceType: ResourceType,
121 policyByTimeslot: SortedMap[Timeslot, PolicyModel],
122 agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
123 clogOpt: Option[ContextualLogger] = None
124 ): List[Chargeslot] = {
126 val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
128 val policyTimeslots = policyByTimeslot.keySet
129 val agreementTimeslots = agreementByTimeslot.keySet
131 def getPolicyWithin(ts: Timeslot): PolicyModel = {
132 policyByTimeslot.find(_._1.contains(ts)).get._2
134 def getAgreementWithin(ts: Timeslot): UserAgreementModel = {
135 agreementByTimeslot.find(_._1.contains(ts)).get._2
138 // 1. Round ONE: split time according to overlapping policies and agreements.
139 val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
141 // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
142 // fine-grained timeslots according to applicable algorithms.
143 // Then pack the info into charge slots.
144 // clog.begin("ROUND 2")
145 val allChargeslots = for {
146 alignedTimeslot <- alignedTimeslots
148 val policy = getPolicyWithin(alignedTimeslot)
149 // clog.debug("dslPolicy = %s", dslPolicy)
150 val userAgreement = getAgreementWithin(alignedTimeslot)
152 // TODO: Factor this out, just like we did with:
153 // TODO: val alignedTimeslots = splitTimeslotByPoliciesAndAgreements
154 // Note that most of the code is already taken from calcChangeChunks()
155 val unitPriceByTimeslot = resolveEffectiveUnitPrices(alignedTimeslot, policy, userAgreement, resourceType, Some(clog))
157 // Now, the timeslots must be the same
158 val finegrainedTimeslots = unitPriceByTimeslot.keySet
160 val chargeslots = for (finegrainedTimeslot ← finegrainedTimeslots) yield {
162 finegrainedTimeslot.from.getTime,
163 finegrainedTimeslot.to.getTime,
164 unitPriceByTimeslot(finegrainedTimeslot)
171 val result = allChargeslots.flatten
177 * Compute the charge slots generated by a particular resource event.
180 def computeFullChargeslots(
181 previousResourceEventOpt: Option[ResourceEventModel],
182 currentResourceEvent: ResourceEventModel,
184 oldTotalAmount: Double,
185 newTotalAmount: Double,
186 resourceType: ResourceType,
187 agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
188 policyStore: PolicyStore,
189 clogOpt: Option[ContextualLogger] = None
190 ): (Timeslot, List[Chargeslot]) = {
192 val clog = ContextualLogger.fromOther(clogOpt, logger, "computeFullChargeslots()")
195 val occurredDate = currentResourceEvent.occurredDate
196 val occurredMillis = currentResourceEvent.occurredMillis
197 val chargingBehavior = resourceType.chargingBehavior
199 val (referenceTimeslot, policyByTimeslot, previousValue) = chargingBehavior.needsPreviousEventForCreditAndAmountCalculation match {
200 // We need a previous event
202 previousResourceEventOpt match {
203 // We have a previous event
204 case Some(previousResourceEvent) ⇒
205 val referenceTimeslot = Timeslot(previousResourceEvent.occurredDate, occurredDate)
206 // all policies within the interval from previous to current resource event
207 // clog.debug("Calling policyStore.loadAndSortPoliciesWithin(%s)", referenceTimeslot)
208 // TODO: store policies in mem?
209 val policyByTimeslot = policyStore.loadAndSortPoliciesWithin(referenceTimeslot.from.getTime, referenceTimeslot.to.getTime)
211 (referenceTimeslot, policyByTimeslot, previousResourceEvent.value)
213 // We do not have a previous event
215 throw new AquariumInternalError(
216 "Unable to charge. No previous event given for %s".format(currentResourceEvent.toDebugString))
219 // We do not need a previous event
221 // ... so we cannot compute timedelta from a previous event, there is just one chargeslot
222 // referring to (almost) an instant in time
223 val previousValue = chargingBehavior.getResourceInstanceUndefinedAmount
225 // TODO: Check semantics of this
226 val referenceTimeslot = Timeslot(new MutableDateCalc(occurredDate).goPreviousMilli.toDate, occurredDate)
228 // TODO: store policies in mem?
229 val relevantPolicyOpt: Option[PolicyModel] = policyStore.loadValidPolicyAt(occurredMillis)
231 val policyByTimeslot = relevantPolicyOpt match {
232 case Some(relevantPolicy) ⇒
233 SortedMap(referenceTimeslot -> relevantPolicy)
236 throw new AquariumInternalError("No relevant policy found for %s".format(referenceTimeslot))
239 (referenceTimeslot, policyByTimeslot, previousValue)
242 val initialChargeslots = computeInitialChargeslots(
250 val fullChargeslots = initialChargeslots.map {
251 case chargeslot@Chargeslot(startMillis, stopMillis, unitPrice, _) ⇒
252 val valueMap = chargingBehavior.makeValueMap(
256 stopMillis - startMillis,
258 currentResourceEvent.value,
262 // clog.debug("execAlgorithm = %s", execAlgorithm)
263 clog.debugMap("valueMap", valueMap, 1)
266 val credits = SimpleExecutableChargingBehaviorAlgorithm.apply(valueMap)
267 chargeslot.copyWithCredits(credits)
270 val result = referenceTimeslot -> fullChargeslots
276 * Given two lists of timeslots, produce a list which contains the
277 * set of timeslot slices, as those are defined by
280 * For example, given the timeslots a and b below, split them as shown.
282 * a = |****************|
289 * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
291 private[computation] def alignTimeslots(a: List[Timeslot],
292 b: List[Timeslot]): List[Timeslot] = {
294 def safeTail(foo: List[Timeslot]) = foo match {
296 case x :: Nil => List()
297 case x :: rest => rest
300 if(a.isEmpty) return b
301 if(b.isEmpty) return a
303 assert(a.head.from == b.head.from)
305 if(a.head.endsAfter(b.head)) {
306 val slice = a.head.slice(b.head.to)
307 slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
308 } else if(b.head.endsAfter(a.head)) {
309 val slice = b.head.slice(a.head.to)
310 slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
312 a.head :: alignTimeslots(safeTail(a), safeTail(b))
316 type PriceMap = immutable.SortedMap[Timeslot, EffectiveUnitPrice]
317 private type PriceList = List[EffectiveUnitPrice]
318 private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPrice]()
321 * Resolves the effective price list for each chunk of the
322 * provided timeslot and returns it as a Map
324 private def resolveEffectiveUnitPricesForTimeslot(
325 alignedTimeslot: Timeslot,
327 agreement: UserAgreementModel,
328 resourceType: ResourceType
331 val role = agreement.role
332 val fullPriceTable = agreement.fullPriceTableRef match {
333 case PolicyDefinedFullPriceTableRef ⇒
334 policy.roleMapping.get(role) match {
335 case Some(fullPriceTable) ⇒
339 throw new AquariumInternalError("Unknown role %s".format(role))
342 case AdHocFullPriceTableRef(fullPriceTable) ⇒
346 val effectivePriceTable = fullPriceTable.perResource.get(resourceType.name) match {
348 throw new AquariumInternalError("Unknown resource type %s".format(role))
350 case Some(effectivePriceTable) ⇒
354 resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
357 private def printPriceList(p: PriceList) : Unit = {
358 Console.err.println("BEGIN PRICE LIST")
359 for { p1 <- p } Console.err.println(p1)
360 Console.err.println("END PRICE LIST")
363 private def printPriceMap(m: PriceMap) = {
364 Console.err.println("BEGIN PRICE MAP")
365 for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p)
366 Console.err.println("END PRICE MAP")
369 private def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
370 Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
372 val ret = resolveEffective3(alignedTimeslot,p) //HERE
378 private def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
379 effectiveUnitPrices match {
383 val (satisfied,notSatisfied) = hd splitTimeslot alignedTimeslot
384 val satisfiedMap = satisfied.foldLeft (emptyMap) {(map,t) =>
385 //Console.err.println("Adding timeslot" + t +
386 // " for policy " + policy.name)
389 val notSatisfiedMap = notSatisfied.foldLeft (emptyMap) {(map,t) =>
390 val otherMap = resolveEffective3(t,tl)
391 //Console.err.println("Residual timeslot: " + t)
392 val ret = map ++ otherMap
395 val ret = satisfiedMap ++ notSatisfiedMap