2 * Copyright 2011 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.logic.accounting.dsl
38 import com.ckkloverdos.maybe.{NoVal, Failed, Just, Maybe}
41 * A cost policy indicates how charging for a resource will be done
42 * wrt the various states a resource can be.
44 * @author Georgios Gousios <gousiosg@gmail.com>
45 * @author Christos KK Loverdos <loverdos@gmail.com>
48 abstract class DSLCostPolicy(val name: String) extends DSLItem {
50 def isOnOff: Boolean = isNamed(DSLCostPolicyNames.onoff)
52 def isContinuous: Boolean = isNamed(DSLCostPolicyNames.continuous)
54 def isDiscrete: Boolean = isNamed(DSLCostPolicyNames.discrete)
56 def isNamed(aName: String): Boolean = aName == name
58 def needsPreviousEventForCreditCalculation: Boolean
61 * True if the resource event has an absolute value.
63 def resourceEventValueIsAbs: Boolean = !resourceEventValueIsDiff
64 def resourceEventValueIsDiff: Boolean = !resourceEventValueIsAbs
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.
71 * At the beginning of the billing period, a resource's absolute value can either:
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.
77 def needsAbsValueForCreditCalculation: Boolean = !needsDiffValueForCreditCalculation
78 def needsDiffValueForCreditCalculation: Boolean = !needsAbsValueForCreditCalculation
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.
84 def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
86 def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double
88 def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double
91 * Th every initial amount.
93 def getResourceInstanceInitialAmount: Double
96 * Get the value that will be used in credit calculation in Accounting.chargeEvents
98 def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
100 def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double
103 * An event's value by itself should carry enough info to characterize it billable or not.
105 * Typically all events are billable by default and indeed this is the default implementation
108 * The only exception to the rule is ON events for [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
110 def isBillableEventBasedOnValue(eventValue: Double): Boolean = true
113 object DSLCostPolicyNames {
114 final val onoff = "onoff"
115 final val discrete = "discrete"
116 final val continuous = "continuous"
119 object DSLCostPolicy {
120 def apply(name: String): DSLCostPolicy = {
123 throw new DSLParseException("<null> cost policy")
125 case name ⇒ name.toLowerCase match {
126 case DSLCostPolicyNames.onoff ⇒ OnOffCostPolicy
127 case DSLCostPolicyNames.discrete ⇒ DiscreteCostPolicy
128 case DSLCostPolicyNames.continuous ⇒ ContinuousCostPolicy
131 throw new DSLParseException("Invalid cost policy %s".format(name))
138 * In practice a resource usage will be charged for the total amount of usage
139 * between resource usage changes.
141 * Example resource that might be adept to a continuous policy
144 case object ContinuousCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.continuous) {
145 def needsPreviousEventForCreditCalculation: Boolean = true
147 override def needsAbsValueForCreditCalculation = true
149 override def resourceEventValueIsDiff = true
151 def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
153 case Just(oldAmount) ⇒
154 Just(oldAmount + newEventValue)
156 Failed(new Exception("NoVal for oldValue instead of Just"))
158 Failed(new Exception("Failed for oldValue instead of Just", e), m)
162 def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
163 oldAmount + newEventValue
166 def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
170 def getResourceInstanceInitialAmount: Double = {
174 def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
178 def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
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.
191 * Example resources that might be adept to onoff policies are VMs in a
192 * cloud application and books in a book lending application.
194 case object OnOffCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.onoff) {
195 def needsPreviousEventForCreditCalculation: Boolean = true
197 override def needsAbsValueForCreditCalculation = true
199 override def resourceEventValueIsAbs = true
201 def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
205 def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
209 def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
210 import OnOffCostPolicyValues.{ON, OFF}
212 case ON ⇒ /* implicit off at the end of the billing period */ OFF
217 def getResourceInstanceInitialAmount: Double = {
221 def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
223 case Just(oldAmount) ⇒
224 Maybe(getValueForCreditCalculation(oldAmount, newEventValue))
226 Failed(new Exception("NoVal for oldValue instead of Just"))
228 Failed(new Exception("Failed for oldValue instead of Just", e), m)
232 def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
233 import OnOffCostPolicyValues.{ON, OFF}
235 def exception(rs: OnOffPolicyResourceState) =
236 new Exception("Resource state transition error (%s -> %s)".format(rs, rs))
238 (oldAmount, newEventValue) match {
240 throw exception(OnResourceState)
246 throw exception(OffResourceState)
250 override def isBillableEventBasedOnValue(eventValue: Double) = {
251 // ON events do not contribute, only OFF ones.
252 OnOffCostPolicyValues.isOFF(eventValue)
256 object OnOffCostPolicyValues {
257 final val ON : Double = 1.0
258 final val OFF: Double = 0.0
260 def isON (value: Double) = value == ON
261 def isOFF(value: Double) = value == OFF
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.
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)
273 case object DiscreteCostPolicy extends DSLCostPolicy(DSLCostPolicyNames.discrete) {
274 def needsPreviousEventForCreditCalculation: Boolean = false
276 override def needsDiffValueForCreditCalculation = true
278 override def resourceEventValueIsDiff = true
280 def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
281 oldAmountM.map(_ + newEventValue)
284 def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
285 oldAmount + newEventValue
288 def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
289 0.0 // ?? def getResourceInstanceInitialAmount
292 def getResourceInstanceInitialAmount: Double = {
296 def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
300 def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
306 * Encapsulates the possible states that a resource with an
307 * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]]
310 abstract class OnOffPolicyResourceState(val state: String) {
311 def isOn: Boolean = !isOff
312 def isOff: Boolean = !isOn
315 object OnOffPolicyResourceState {
316 def apply(name: Any): OnOffPolicyResourceState = {
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))
329 object OnOffPolicyResourceStateNames {
331 final val off = "off"
334 object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.on) {
335 override def isOn = true
337 object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.off) {
338 override def isOff = true