Pruning stuff from user state
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / accounting / dsl / DSLCostPolicy.scala
1 /*
2  * Copyright 2011 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.logic.accounting.dsl
37
38 import com.ckkloverdos.maybe.{NoVal, Failed, Just, Maybe}
39
40 /**
41  * A cost policy indicates how charging for a resource will be done
42  * wrt the various states a resource can be.
43  *
44  * @author Georgios Gousios <gousiosg@gmail.com>
45  * @author Christos KK Loverdos <loverdos@gmail.com>
46  */
47
48 abstract class DSLCostPolicy(val name: String) extends DSLItem {
49
50   def isOnOff: Boolean = isNamed(DSLCostPolicyNames.onoff)
51
52   def isContinuous: Boolean = isNamed(DSLCostPolicyNames.continuous)
53
54   def isDiscrete: Boolean = isNamed(DSLCostPolicyNames.discrete)
55   
56   def isNamed(aName: String): Boolean = aName == name
57   
58   def needsPreviousEventForCreditCalculation: Boolean
59
60   /**
61    * True if the resource event has an absolute value.
62    */
63   def resourceEventValueIsAbs: Boolean = !resourceEventValueIsDiff
64   def resourceEventValueIsDiff: Boolean = !resourceEventValueIsAbs
65
66   /**
67    * True if an absolute value is needed for the credit calculation (for example: diskspace).
68    * An absolute value is obtained using the beginning of the billing period as a reference point
69    * and is modified every time a new resource event arrives.
70    *
71    * At the beginning of the billing period, a resource's absolute value can either:
72    * a) Set to zero
73    * b) Set to a non-zero, constant value
74    * c) Set to a varying value, based on some function
75    * d) Not change at all. For this case, we need some initial value to start with.
76    */
77   def needsAbsValueForCreditCalculation: Boolean = !needsDiffValueForCreditCalculation
78   def needsDiffValueForCreditCalculation: Boolean = !needsAbsValueForCreditCalculation
79
80   /**
81    * Given the old amount of a resource instance (see [[gr.grnet.aquarium.user.ResourceInstanceSnapshot]]) and the
82    * value arriving in a new resource event, compute the new instance amount.
83    */
84   def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
85
86   def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double
87
88   def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double
89
90   /**
91    * Th every initial amount.
92    */
93   def getResourceInstanceInitialAmount: Double
94
95   /**
96    * Get the value that will be used in credit calculation in Accounting.chargeEvents
97    */
98   def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
99
100   def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double
101
102   /**
103    * An event's value by itself should carry enough info to characterize it billable or not.
104    *
105    * Typically all events are billable by default and indeed this is the default implementation
106    * provided here.
107    *
108    * The only exception to the rule is ON events for [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
109    */
110   def isBillableEventBasedOnValue(eventValue: Double): Boolean = true
111 }
112
113 object DSLCostPolicyNames {
114   final val onoff      = "onoff"
115   final val discrete   = "discrete"
116   final val continuous = "continuous"
117 }
118
119 object DSLCostPolicy {
120   def apply(name: String): DSLCostPolicy  = {
121     name match {
122       case null ⇒
123         throw new DSLParseException("<null> cost policy")
124
125       case name ⇒ name.toLowerCase match {
126         case DSLCostPolicyNames.onoff      ⇒ OnOffCostPolicy
127         case DSLCostPolicyNames.discrete   ⇒ DiscreteCostPolicy
128         case DSLCostPolicyNames.continuous ⇒ ContinuousCostPolicy
129
130         case _ ⇒
131           throw new DSLParseException("Invalid cost policy %s".format(name))
132       }
133     }
134   }
135 }
136
137 /**
138  * In practice a resource usage will be charged for the total amount of usage
139  * between resource usage changes.
140  *
141  * Example resource that might be adept to a continuous policy
142  * is diskspace.
143  */
144 case object ContinuousCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.continuous) {
145   def needsPreviousEventForCreditCalculation: Boolean = true
146
147   override def needsAbsValueForCreditCalculation = true
148
149   override def resourceEventValueIsDiff = true
150
151   def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
152     oldAmountM match {
153       case Just(oldAmount) ⇒
154         Just(oldAmount + newEventValue)
155       case NoVal ⇒
156         Failed(new Exception("NoVal for oldValue instead of Just"))
157       case Failed(e, m) ⇒
158         Failed(new Exception("Failed for oldValue instead of Just", e), m)
159     }
160   }
161
162   def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
163     oldAmount + newEventValue
164   }
165
166   def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
167     oldAmount
168   }
169
170   def getResourceInstanceInitialAmount: Double = {
171     0.0
172   }
173
174   def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
175     oldAmountM
176   }
177
178   def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
179     oldAmount
180   }
181
182 }
183
184 /**
185  * An onoff cost policy expects a resource to be in one of the two allowed
186  * states (`on` and `off`, respectively). It will charge for resource usage
187  * within the timeframes specified by consecutive on and off resource events.
188  * An onoff policy is the same as a continuous policy, except for
189  * the timeframes within the resource is in the `off` state.
190  *
191  * Example resources that might be adept to onoff policies are VMs in a
192  * cloud application and books in a book lending application.
193  */
194 case object OnOffCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.onoff) {
195   def needsPreviousEventForCreditCalculation: Boolean = true
196
197   override def needsAbsValueForCreditCalculation = true
198
199   override def resourceEventValueIsAbs = true
200
201   def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
202     Just(newEventValue)
203   }
204
205   def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
206     newEventValue
207   }
208   
209   def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
210     import OnOffCostPolicyValues.{ON, OFF}
211     oldAmount match {
212       case ON  ⇒ /* implicit off at the end of the billing period */ OFF
213       case OFF ⇒ OFF
214     }
215   }
216
217   def getResourceInstanceInitialAmount: Double = {
218     0.0
219   }
220   
221   def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
222     oldAmountM match {
223       case Just(oldAmount) ⇒
224         Maybe(getValueForCreditCalculation(oldAmount, newEventValue))
225       case NoVal ⇒
226         Failed(new Exception("NoVal for oldValue instead of Just"))
227       case Failed(e, m) ⇒
228         Failed(new Exception("Failed for oldValue instead of Just", e), m)
229     }
230   }
231
232   def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
233     import OnOffCostPolicyValues.{ON, OFF}
234
235     def exception(rs: OnOffPolicyResourceState) =
236       new Exception("Resource state transition error (%s -> %s)".format(rs, rs))
237
238     (oldAmount, newEventValue) match {
239       case (ON, ON) ⇒
240         throw exception(OnResourceState)
241       case (ON, OFF) ⇒
242         OFF
243       case (OFF, ON) ⇒
244         ON
245       case (OFF, OFF) ⇒
246         throw exception(OffResourceState)
247     }
248   }
249
250   override def isBillableEventBasedOnValue(eventValue: Double) = {
251     // ON events do not contribute, only OFF ones.
252     OnOffCostPolicyValues.isOFF(eventValue)
253   }
254 }
255
256 object OnOffCostPolicyValues {
257   final val ON : Double = 1.0
258   final val OFF: Double = 0.0
259
260   def isON (value: Double) = value == ON
261   def isOFF(value: Double) = value == OFF
262 }
263
264 /**
265  * An discrete cost policy indicates that a resource should be charged directly
266  * at each resource state change, i.e. the charging is not dependent on
267  * the time the resource.
268  *
269  * Example oneoff resources might be individual charges applied to various
270  * actions (e.g. the fact that a user has created an account) or resources
271  * that should be charged per volume once (e.g. the allocation of a volume)
272  */
273 case object DiscreteCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.discrete) {
274   def needsPreviousEventForCreditCalculation: Boolean = false
275
276   override def needsDiffValueForCreditCalculation = true
277
278   override def resourceEventValueIsDiff = true
279
280   def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
281     oldAmountM.map(_ + newEventValue)
282   }
283
284   def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
285     oldAmount + newEventValue
286   }
287
288   def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double  = {
289     0.0 // ?? def getResourceInstanceInitialAmount
290   }
291
292   def getResourceInstanceInitialAmount: Double = {
293     0.0
294   }
295   
296   def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
297     Just(newEventValue)
298   }
299
300   def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
301     newEventValue
302   }
303 }
304
305 /**
306  * Encapsulates the possible states that a resource with an
307  * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]]
308  * can be.
309  */
310 abstract class OnOffPolicyResourceState(val state: String) {
311   def isOn: Boolean = !isOff
312   def isOff: Boolean = !isOn
313 }
314
315 object OnOffPolicyResourceState {
316   def apply(name: Any): OnOffPolicyResourceState = {
317     name match {
318       case x: String if (x.equalsIgnoreCase(OnOffPolicyResourceStateNames.on))  => OnResourceState
319       case y: String if (y.equalsIgnoreCase(OnOffPolicyResourceStateNames.off)) => OffResourceState
320       case a: Double if (a == 0) => OffResourceState
321       case b: Double if (b == 1) => OnResourceState
322       case i: Int if (i == 0) => OffResourceState
323       case j: Int if (j == 1) => OnResourceState
324       case _ => throw new DSLParseException("Invalid OnOffPolicyResourceState %s".format(name))
325     }
326   }
327 }
328
329 object OnOffPolicyResourceStateNames {
330   final val on  = "on"
331   final val off = "off"
332 }
333
334 object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.on) {
335   override def isOn = true
336 }
337 object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.off) {
338   override def isOff = true
339 }