Fixed data race in caching polict store. Restored some code from timeslot computations
[aquarium] / src / main / scala / gr / grnet / aquarium / computation / TimeslotComputations.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
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.
16  *
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.
29  *
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.
34  */
35
36 package gr.grnet.aquarium.computation
37
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
49
50 /**
51  * Methods for converting accounting events to wallet entries.
52  *
53  * @author Georgios Gousios <gousiosg@gmail.com>
54  * @author Christos KK Loverdos <loverdos@gmail.com>
55  */
56 object TimeslotComputations extends Loggable {
57
58   /**
59    * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
60    *
61    * @param referenceTimeslot
62    * @param policyTimeslots
63    * @param agreementTimeslots
64    * @return
65    */
66   protected
67   def splitTimeslotByPoliciesAndAgreements(
68       referenceTimeslot: Timeslot,
69       policyTimeslots: List[Timeslot],
70       agreementTimeslots: List[Timeslot],
71       clogM: Maybe[ContextualLogger] = NoVal
72   ): List[Timeslot] = {
73
74     // Align policy and agreement validity timeslots to the referenceTimeslot
75     val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
76     val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
77
78     val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
79
80     result
81   }
82
83   /**
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.
86    *
87    */
88   protected
89   def resolveEffectiveUnitPrices(
90       alignedTimeslot: Timeslot,
91       policy: PolicyModel,
92       agreement: UserAgreementModel,
93       resourceType: ResourceType,
94       clogOpt: Option[ContextualLogger] = None
95   ): SortedMap[Timeslot, Double] = {
96
97     val clog = ContextualLogger.fromOther(clogOpt, logger, "resolveEffectiveUnitPrices()")
98
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)}
102   }
103
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] = {
111
112     val clog = ContextualLogger.fromOther(clogOpt, logger, "computeInitialChargeslots()")
113
114     val policyTimeslots = policyByTimeslot.keySet
115     val agreementTimeslots = agreementByTimeslot.keySet
116
117     def getPolicyWithin(ts: Timeslot): PolicyModel = {
118       policyByTimeslot.find(_._1.contains(ts)).get._2
119     }
120     def getAgreementWithin(ts: Timeslot): UserAgreementModel = {
121       agreementByTimeslot.find(_._1.contains(ts)).get._2
122     }
123
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))
127
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
134     } yield {
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)
140
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))
145
146       // Now, the timeslots must be the same
147       val finegrainedTimeslots = unitPriceByTimeslot.keySet
148
149       val chargeslots = for (finegrainedTimeslot ← finegrainedTimeslots) yield {
150         Chargeslot(
151           finegrainedTimeslot.from.getTime,
152           finegrainedTimeslot.to.getTime,
153           unitPriceByTimeslot(finegrainedTimeslot)
154         )
155       }
156
157       chargeslots.toList
158     }
159
160     val result = allChargeslots.flatten
161
162     result
163   }
164
165   /**
166    * Given two lists of timeslots, produce a list which contains the
167    * set of timeslot slices, as those are defined by
168    * timeslot overlaps.
169    *
170    * For example, given the timeslots a and b below, split them as shown.
171    *
172    * a = |****************|
173    * ^                ^
174    * a.from            a.to
175    * b = |*********|
176    * ^         ^
177    * b.from     b.to
178    *
179    * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
180    */
181   private[computation] def alignTimeslots(a: List[Timeslot],
182                                     b: List[Timeslot]): List[Timeslot] = {
183
184     def safeTail(foo: List[Timeslot]) = foo match {
185       case Nil => List()
186       case x :: Nil => List()
187       case x :: rest => rest
188     }
189
190     if(a.isEmpty) return b
191     if(b.isEmpty) return a
192
193     assert(a.head.from == b.head.from)
194
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)
201     } else {
202       a.head :: alignTimeslots(safeTail(a), safeTail(b))
203     }
204   }
205
206     type PriceMap =  immutable.SortedMap[Timeslot, EffectiveUnitPrice]
207     private type PriceList = List[EffectiveUnitPrice]
208     private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPrice]()
209
210     /**
211      * Resolves the effective price list for each chunk of the
212      * provided timeslot and returns it as a Map
213      */
214     private def resolveEffectiveUnitPricesForTimeslot(
215                                                alignedTimeslot: Timeslot,
216                                                policy: PolicyModel,
217                                                agreement: UserAgreementModel,
218                                                resourceType: ResourceType
219                                                ): PriceMap = {
220
221       val role = agreement.role
222       val fullPriceTable = agreement.fullPriceTableRef match {
223         case PolicyDefinedFullPriceTableRef ⇒
224           policy.roleMapping.get(role) match {
225             case Some(fullPriceTable) ⇒
226               fullPriceTable
227
228             case None ⇒
229               throw new AquariumInternalError("Unknown role %s".format(role))
230           }
231
232         case AdHocFullPriceTableRef(fullPriceTable) ⇒
233           fullPriceTable
234       }
235
236       val effectivePriceTable = fullPriceTable.perResource.get(resourceType.name) match {
237         case None ⇒
238           throw new AquariumInternalError("Unknown resource type %s".format(role))
239
240         case Some(effectivePriceTable) ⇒
241           effectivePriceTable
242       }
243
244       resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
245       //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
246     }
247
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")
252     }
253
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")
258     }
259
260     private def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
261       Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
262       printPriceList(p)
263       val ret =  resolveEffective3(alignedTimeslot,p) //HERE
264       printPriceMap(ret)
265       ret
266     }
267
268
269     private def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
270       effectiveUnitPrices match {
271         case Nil =>
272           emptyMap
273         case hd::tl =>
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)
278             map + ((t,hd))
279           }
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
284             ret
285           }
286           val ret = satisfiedMap ++ notSatisfiedMap
287           ret
288       }
289 }