Now using schema-based messages
[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 gr.grnet.aquarium.util.Loggable
40 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
41 import gr.grnet.aquarium.policy._
42 import collection.immutable
43 import gr.grnet.aquarium.policy.EffectiveUnitPriceModel
44 import gr.grnet.aquarium.message.avro.gen.{EffectiveUnitPriceMsg, PolicyMsg, UserAgreementMsg, FullPriceTableMsg, EffectivePriceTableMsg, ChargeslotMsg}
45
46 /**
47  * Methods for converting accounting events to wallet entries.
48  *
49  * @author Georgios Gousios <gousiosg@gmail.com>
50  * @author Christos KK Loverdos <loverdos@gmail.com>
51  */
52 object TimeslotComputations extends Loggable {
53
54   /**
55    * Breaks a reference timeslot (e.g. billing period) according to policies and agreements.
56    *
57    * @param referenceTimeslot
58    * @param policyTimeslots
59    * @param agreementTimeslots
60    * @return
61    */
62   private[this] def splitTimeslotByPoliciesAndAgreements(
63       referenceTimeslot: Timeslot,
64       policyTimeslots: List[Timeslot],
65       agreementTimeslots: List[Timeslot]
66   ): List[Timeslot] = {
67
68     // Align policy and agreement validity timeslots to the referenceTimeslot
69     val alignedPolicyTimeslots = referenceTimeslot.align(policyTimeslots)
70     val alignedAgreementTimeslots = referenceTimeslot.align(agreementTimeslots)
71
72     val result = alignTimeslots(alignedPolicyTimeslots, alignedAgreementTimeslots)
73
74     result
75   }
76
77   /**
78    * Given a reference timeslot, we have to break it up to a series of timeslots where a particular
79    * algorithm and price unit is in effect.
80    *
81    */
82   private[this] def resolveEffectiveUnitPrices(
83       alignedTimeslot: Timeslot,
84       policy: PolicyModel,
85       agreement: UserAgreementModel,
86       fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
87       effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
88   ): SortedMap[Timeslot, Double] = {
89
90     // Note that most of the code is taken from calcChangeChunks()
91     val ret = resolveEffectiveUnitPricesForTimeslot(
92       alignedTimeslot,
93       policy,
94       agreement,
95       fullPriceTableModelGetter,
96       effectivePriceTableModelSelector)
97     ret map {case (t,p) => (t,p.unitPrice)}
98   }
99
100   def computeInitialChargeslots(
101       referenceTimeslot: Timeslot,
102       policyByTimeslot: SortedMap[Timeslot, PolicyModel],
103       agreementByTimeslot: SortedMap[Timeslot, UserAgreementModel],
104       fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
105       effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
106   ): List[ChargeslotMsg] = {
107
108     val policyTimeslots = policyByTimeslot.keySet
109     val agreementTimeslots = agreementByTimeslot.keySet
110
111     def getPolicyWithin(ts: Timeslot): PolicyModel = {
112       policyByTimeslot.find(_._1.contains(ts)).get._2
113     }
114     def getAgreementWithin(ts: Timeslot): UserAgreementModel = {
115       agreementByTimeslot.find(_._1.contains(ts)).get._2
116     }
117
118     // 1. Round ONE: split time according to overlapping policies and agreements.
119     //val alignedTimeslots = List(referenceTimeslot) //splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList, Just(clog))
120     val alignedTimeslots = splitTimeslotByPoliciesAndAgreements(referenceTimeslot, policyTimeslots.toList, agreementTimeslots.toList)
121
122     // 2. Round TWO: Use the aligned timeslots of Round ONE to produce even more
123     //    fine-grained timeslots according to applicable algorithms.
124     //    Then pack the info into charge slots.
125     //    clog.begin("ROUND 2")
126     val allChargeslots = for {
127       alignedTimeslot <- alignedTimeslots
128     } yield {
129       //val policy = policyByTimeslot.valuesIterator.next()//getPolicyWithin(alignedTimeslot)
130       val policy = getPolicyWithin(alignedTimeslot)
131       //      clog.debug("dslPolicy = %s", dslPolicy)
132       //val userAgreement = agreementByTimeslot.valuesIterator.next()//getAgreementWithin(alignedTimeslot)
133       val userAgreement = getAgreementWithin(alignedTimeslot)
134
135       val unitPriceByTimeslot = resolveEffectiveUnitPrices(
136         alignedTimeslot,
137         policy,
138         userAgreement,
139         fullPriceTableModelGetter,
140         effectivePriceTableModelSelector
141       )
142
143       // Now, the timeslots must be the same
144       val finegrainedTimeslots = unitPriceByTimeslot.keySet
145
146       val chargeslots = for(finegrainedTimeslot ← finegrainedTimeslots) yield {
147         val cs = new ChargeslotMsg
148
149         cs.setStartMillis(finegrainedTimeslot.from.getTime)
150         cs.setStopMillis(finegrainedTimeslot.to.getTime)
151         cs.setUnitPrice(unitPriceByTimeslot(finegrainedTimeslot))
152
153         cs
154       }
155
156       chargeslots.toList
157     }
158
159     val result = allChargeslots.flatten
160
161     result
162   }
163
164   /**
165    * Given two lists of timeslots, produce a list which contains the
166    * set of timeslot slices, as those are defined by
167    * timeslot overlaps.
168    *
169    * For example, given the timeslots a and b below, split them as shown.
170    *
171    * a = |****************|
172    * ^                ^
173    * a.from            a.to
174    * b = |*********|
175    * ^         ^
176    * b.from     b.to
177    *
178    * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to))
179    */
180   private[this] def alignTimeslots(
181       a: List[Timeslot],
182       b: List[Timeslot]
183   ): List[Timeslot] = {
184
185     def safeTail(foo: List[Timeslot]) = foo match {
186       case Nil => List()
187       case x :: Nil => List()
188       case x :: rest => rest
189     }
190
191     if(a.isEmpty) return b
192     if(b.isEmpty) return a
193     if(a.head.from != b.head.from)
194     assert(a.head.from == b.head.from)
195
196     if(a.head.endsAfter(b.head)) {
197       val slice = a.head.slice(b.head.to)
198       slice.head :: alignTimeslots(slice.last :: a.tail, safeTail(b))
199     } else if(b.head.endsAfter(a.head)) {
200       val slice = b.head.slice(a.head.to)
201       slice.head :: alignTimeslots(safeTail(a), slice.last :: b.tail)
202     } else {
203       a.head :: alignTimeslots(safeTail(a), safeTail(b))
204     }
205   }
206
207     type PriceMap =  immutable.SortedMap[Timeslot, EffectiveUnitPriceModel]
208     private type PriceList = List[EffectiveUnitPriceModel]
209     private def emptyMap = immutable.SortedMap[Timeslot,EffectiveUnitPriceModel]()
210
211     /**
212      * Resolves the effective price list for each chunk of the
213      * provided timeslot and returns it as a Map
214      */
215     private[this] def resolveEffectiveUnitPricesForTimeslot(
216         alignedTimeslot: Timeslot,
217         policy: PolicyModel,
218         userAgreement: UserAgreementModel,
219         fullPriceTableModelGetter: (UserAgreementModel, PolicyModel) ⇒ FullPriceTableModel,
220         effectivePriceTableModelSelector: FullPriceTableModel ⇒ EffectivePriceTableModel
221     ): PriceMap = {
222
223       val fullPriceTable = fullPriceTableModelGetter(userAgreement, policy)
224       val effectivePriceTable = effectivePriceTableModelSelector(fullPriceTable)
225
226       resolveEffective(alignedTimeslot, effectivePriceTable.priceOverrides)
227       //immutable.SortedMap(alignedTimeslot -> effectivePriceTable.priceOverrides.head)
228     }
229
230   private[this] def printPriceList(p: PriceList) : Unit = {
231       Console.err.println("BEGIN PRICE LIST")
232       for { p1 <- p } Console.err.println(p1)
233       Console.err.println("END PRICE LIST")
234     }
235
236   private[this] def printPriceMap(m: PriceMap) = {
237       Console.err.println("BEGIN PRICE MAP")
238       for { (t,p) <- m.toList } Console.err.println("Timeslot " + t + "\t\t" + p)
239       Console.err.println("END PRICE MAP")
240     }
241
242   private[this] def resolveEffective(alignedTimeslot: Timeslot,p:PriceList): PriceMap = {
243       //Console.err.println("\n\nInput timeslot: " + alignedTimeslot + "\n\n")
244       //printPriceList(p)
245       val ret =  resolveEffective3(alignedTimeslot,p) //HERE
246       //printPriceMap(ret)
247       ret
248     }
249
250
251   private[this] def resolveEffective3(alignedTimeslot: Timeslot, effectiveUnitPrices: PriceList): PriceMap =
252       effectiveUnitPrices match {
253         case Nil =>
254           emptyMap
255         case hd::tl =>
256           val (satisfied,notSatisfied) = hd splitTimeslot alignedTimeslot
257           val satisfiedMap = satisfied.foldLeft (emptyMap)  {(map,t) =>
258           //Console.err.println("Adding timeslot" + t +
259           // " for policy " + policy.name)
260             map + ((t,hd))
261           }
262           val notSatisfiedMap = notSatisfied.foldLeft (emptyMap) {(map,t) =>
263             val otherMap = resolveEffective3(t,tl)
264             //Console.err.println("Residual timeslot: " + t)
265             val ret = map ++ otherMap
266             ret
267           }
268           val ret = satisfiedMap ++ notSatisfiedMap
269           ret
270       }
271 }