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}
40 import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
41 import gr.grnet.aquarium.AquariumInternalError
42 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
43 import gr.grnet.aquarium.policy._
44 import collection.immutable
45 import com.ckkloverdos.maybe.Just
46 import gr.grnet.aquarium.policy.ResourceType
47 import gr.grnet.aquarium.policy.EffectiveUnitPrice
48 import gr.grnet.aquarium.charging.Chargeslot
51 * Methods for converting accounting events to wallet entries.
53 * @author Georgios Gousios <gousiosg@gmail.com>
54 * @author Christos KK Loverdos <loverdos@gmail.com>
56 object TimeslotComputations extends Loggable {
59 * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
61 * @param referenceTimeslot
62 * @param policyTimeslots
63 * @param agreementTimeslots
67 def splitTimeslotByPoliciesAndAgreements(
68 referenceTimeslot: Timeslot,
69 policyTimeslots: List[Timeslot],
70 agreementTimeslots: List[Timeslot],
71 clogM: Maybe[ContextualLogger] = NoVal
74 // Align policy and agreement validity timeslots to the referenceTimeslot
75 val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
76 val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
78 val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
84 * Given a reference timeslot, we have to break it up to a series of timeslots where a particular
85 * algorithm and price unit is in effect.
89 def resolveEffectiveUnitPrices(
90 alignedTimeslot: Timeslot,
92 agreement: UserAgreementModel,
93 resourceType: ResourceType,
94 clogOpt: Option[ContextualLogger] = None
95 ): SortedMap[Timeslot, Double] = {
97 val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
99 // Note that most of the code is taken from calcChangeChunks()
100 val ret = resolveEffectiveUnitPricesForTimeslot(alignedTimeslot, policy, agreement, resourceType)
101 ret map {case (t,p) => (t,p.unitPrice)}
104 def computeInitialChargeslots(
105 referenceTimeslot: Timeslot,
106 resourceType: ResourceType,
107 policyByTimeslot: SortedMap[Timeslot, PolicyModel],
108 agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
109 clogOpt: Option[ContextualLogger] = None
110 ): List[Chargeslot] = {
112 val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
114 val policyTimeslots = policyByTimeslot.keySet
115 val agreementTimeslots = agreementByTimeslot.keySet
117 def getPolicyWithin(ts: Timeslot): PolicyModel = {
118 policyByTimeslot.find(_._1.contains(ts)).get._2
120 def getAgreementWithin(ts: Timeslot): UserAgreementModel = {
121 agreementByTimeslot.find(_._1.contains(ts)).get._2
124 // 1. Round ONE: split time according to overlapping policies and agreements.
125 //val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
126 val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
128 // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
129 // fine-grained timeslots according to applicable algorithms.
130 // Then pack the info into charge slots.
131 // clog.begin("ROUND 2")
132 val allChargeslots = for {
133 alignedTimeslot <- alignedTimeslots
135 //val policy = policyByTimeslot.valuesIterator.next()//getPolicyWithin(alignedTimeslot)
136 val policy = getPolicyWithin(alignedTimeslot)
137 // clog.debug("dslPolicy = %s", dslPolicy)
138 //val userAgreement = agreementByTimeslot.valuesIterator.next()//getAgreementWithin(alignedTimeslot)
139 val userAgreement = getAgreementWithin(alignedTimeslot)
141 // TODO: Factor this out, just like we did with:
142 // TODO: val alignedTimeslots = splitTimeslotByPoliciesAndAgreements
143 // Note that most of the code is already taken from calcChangeChunks()
144 val unitPriceByTimeslot = resolveEffectiveUnitPrices(alignedTimeslot, policy, userAgreement, resourceType, Some(clog))
146 // Now, the timeslots must be the same
147 val finegrainedTimeslots = unitPriceByTimeslot.keySet
149 val chargeslots = for (finegrainedTimeslot ← finegrainedTimeslots) yield {
151 finegrainedTimeslot.from.getTime,
152 finegrainedTimeslot.to.getTime,
153 unitPriceByTimeslot(finegrainedTimeslot)
160 val result = allChargeslots.flatten
166 * Given two lists of timeslots, produce a list which contains the
167 * set of timeslot slices, as those are defined by
170 * For example, given the timeslots a and b below, split them as shown.
172 * a = |****************|
179 * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
181 private[computation] def alignTimeslots(a: List[Timeslot],
182 b: List[Timeslot]): List[Timeslot] = {
184 def safeTail(foo: List[Timeslot]) = foo match {
186 case x :: Nil => List()
187 case x :: rest => rest
190 if(a.isEmpty) return b
191 if(b.isEmpty) return a
193 assert(a.head.from == b.head.from)
195 if(a.head.endsAfter(b.head)) {
196 val slice = a.head.slice(b.head.to)
197 slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
198 } else if(b.head.endsAfter(a.head)) {
199 val slice = b.head.slice(a.head.to)
200 slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
202 a.head :: alignTimeslots(safeTail(a), safeTail(b))
206 type PriceMap = immutable.SortedMap[Timeslot, EffectiveUnitPrice]
207 private type PriceList = List[EffectiveUnitPrice]
208 private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPrice]()
211 * Resolves the effective price list for each chunk of the
212 * provided timeslot and returns it as a Map
214 private def resolveEffectiveUnitPricesForTimeslot(
215 alignedTimeslot: Timeslot,
217 agreement: UserAgreementModel,
218 resourceType: ResourceType
221 val role = agreement.role
222 val fullPriceTable = agreement.fullPriceTableRef match {
223 case PolicyDefinedFullPriceTableRef ⇒
224 policy.roleMapping.get(role) match {
225 case Some(fullPriceTable) ⇒
229 throw new AquariumInternalError("Unknown role %s".format(role))
232 case AdHocFullPriceTableRef(fullPriceTable) ⇒
236 val effectivePriceTable = fullPriceTable.perResource.get(resourceType.name) match {
238 throw new AquariumInternalError("Unknown resource type %s".format(role))
240 case Some(effectivePriceTable) ⇒
244 resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
245 //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
248 private def printPriceList(p: PriceList) : Unit = {
249 Console.err.println("BEGIN PRICE LIST")
250 for { p1 <- p } Console.err.println(p1)
251 Console.err.println("END PRICE LIST")
254 private def printPriceMap(m: PriceMap) = {
255 Console.err.println("BEGIN PRICE MAP")
256 for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p)
257 Console.err.println("END PRICE MAP")
260 private def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
261 Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
263 val ret = resolveEffective3(alignedTimeslot,p) //HERE
269 private def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
270 effectiveUnitPrices match {
274 val (satisfied,notSatisfied) = hd splitTimeslot alignedTimeslot
275 val satisfiedMap = satisfied.foldLeft (emptyMap) {(map,t) =>
276 //Console.err.println("Adding timeslot" + t +
277 // " for policy " + policy.name)
280 val notSatisfiedMap = notSatisfied.foldLeft (emptyMap) {(map,t) =>
281 val otherMap = resolveEffective3(t,tl)
282 //Console.err.println("Residual timeslot: " + t)
283 val ret = map ++ otherMap
286 val ret = satisfiedMap ++ notSatisfiedMap