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