# DO NOT TOUCH the following options, unless you know what you are doing
#######
-# Actor subsystem
-actor.provider.class=gr.grnet.aquarium.service.SimpleLocalRoleableActorProviderService
# Class that initializes the REST service
rest.service.class=gr.grnet.aquarium.service.FinagleRESTService
rest.shutdown.timeout.millis=2000
# Store subsystem
store.provider.class=gr.grnet.aquarium.store.mongodb.MongoDBStoreProvider
-# Override the user store (if present, it will not be given by the store provider above)
-#user.state.store.class=gr.grnet.aquarium.store.memory.MemStorede the event store (if present, it will not be given by the store provider above)
-#resource.event.store.class=
-# Override the user event store (if present, it will not be given by the store provider above)
-#user.event.store.class=
-# Override the user event store (if present, it will not be given by the store provider above)
-#policy.store.class=
+
+# A time period in milliseconds for which we can tolerate stale parts regarding user state.
+user.state.timestamp.threshold=1
# Administrative REST API authorization cookie
admin.cookie=1
\ No newline at end of file
--- /dev/null
+{
+ "id":"750E6309-AB60-41B4-8D4B-9FFEA6EF843C",
+
+ "validityTimespan":{
+ "fromMillis":0,
+ "toMillis":9223372036854775807
+ },
+
+ "resourceTypes":[
+ {
+ "name":"bandwidth",
+ "unit":"MB/Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.DiscreteChargingBehavior"
+ },
+ {
+ "name":"vmtime",
+ "unit":"Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.OnOffChargingBehavior"
+ },
+ {
+ "name":"diskspace",
+ "unit":"MB/Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
+ }
+ ],
+
+ "chargingBehaviors":[
+ "gr.grnet.aquarium.charging.DiscreteChargingBehavior",
+ "gr.grnet.aquarium.charging.OnOffChargingBehavior",
+ "gr.grnet.aquarium.charging.ContinuousChargingBehavior",
+ "gr.grnet.aquarium.charging.OnceChargingBehavior"
+ ],
+
+ "roleMapping":{
+ "default":{
+ "perResource":{
+ "bandwidth":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ },
+ "vmtime":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ },
+ "diskspace":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
import gr.grnet.aquarium.Timespan
// Definition of our standard policy in plain Scala
-// This will be dynamically interpreted during Aquarium startup
StdPolicy(
- id = "policy-1",
- parentID = None,
+ id = "750E6309-AB60-41B4-8D4B-9FFEA6EF843C",
+ parentID = None,
- validityTimespan = Timespan(0),
+ validityTimespan = Timespan(0),
- resourceTypes = Set(
- ResourceType("bandwidth", "MB/Hr", DiscreteChargingBehavior),
- ResourceType("vmtime", "Hr", OnOffChargingBehavior),
- ResourceType("diskspace", "MB/Hr", ContinuousChargingBehavior)
- ),
+ resourceTypes = Set(
+ ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+ ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+ ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
+ ),
- chargingBehaviorClasses = Set(
- DiscreteChargingBehavior.getClass.getName,
- OnOffChargingBehavior.getClass.getName,
- ContinuousChargingBehavior.getClass.getName,
- OnceChargingBehavior.getClass.getName
- ),
+ chargingBehaviors = Set(
+ classOf[DiscreteChargingBehavior].getName,
+ classOf[OnOffChargingBehavior].getName,
+ classOf[ContinuousChargingBehavior].getName,
+ classOf[OnceChargingBehavior].getName
+ ),
- roleMapping = Map(
- "default" -> FullPriceTable(Map(
- "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
- "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
- "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
- ))
- )
- )
\ No newline at end of file
+ roleMapping = Map(
+ "default" -> FullPriceTable(Map(
+ "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+ "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+ "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
+ ))
+ )
+)
\ No newline at end of file
import com.ckkloverdos.env.Env
import com.ckkloverdos.key.{IntKey, StringKey, LongKey, TypedKeySkeleton, TypedKey, BooleanKey}
import com.ckkloverdos.props.Props
-import gr.grnet.aquarium.store.{PolicyStore, UserStateStore, IMEventStore, ResourceEventStore, StoreProvider}
+import gr.grnet.aquarium.store.{StoreProvider}
import java.io.File
import gr.grnet.aquarium.util.{Loggable, Lifecycle}
import gr.grnet.aquarium.service.{StoreWatcherService, RabbitMQService, TimerService, EventBusService, AkkaService}
import gr.grnet.aquarium.ResourceLocator._
import com.ckkloverdos.sys.SysProp
import gr.grnet.aquarium.service.event.AquariumCreatedEvent
-import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, UserAgreementModel, ResourceType}
+import gr.grnet.aquarium.policy.{PolicyHistory, PolicyDefinedFullPriceTableRef, StdUserAgreement, UserAgreementModel, ResourceType}
+import gr.grnet.aquarium.charging.ChargingBehavior
/**
*
final class Aquarium(env: Env) extends Lifecycle with Loggable {
import Aquarium.EnvKeys
+ @volatile private[this] var _chargingBehaviorMap = Map[String, ChargingBehavior]()
+
+ private[this] val policyHistory = new PolicyHistory
+
private[this] val _isStopping = new AtomicBoolean(false)
override def toString = "%s/v%s".format(getClass.getName, version)
}
}
- private[this] lazy val _allServices = Aquarium.ServiceKeys.map(this(_))
+ private[this] lazy val _allServices = Aquarium.ServiceKeys.map(this.apply(_))
private[this] def startServices(): Unit = {
for(service ← _allServices) {
logger.info("CONF_HERE = {}", HERE)
logger.info("{} = {}", ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES, ResourceLocator.Resources.AquariumPropertiesResource)
logger.info("{} = {}", ResourceLocator.ResourceNames.LOGBACK_XML, ResourceLocator.Resources.LogbackXMLResource)
- logger.info("{} = {}", ResourceLocator.ResourceNames.POLICY_YAML, ResourceLocator.Resources.PolicyYAMLResource)
+ logger.info("{} = {}", ResourceLocator.ResourceNames.POLICY_JSON, ResourceLocator.Resources.PolicyJSONResource)
logger.info("Runtime.getRuntime.availableProcessors() => {}", Runtime.getRuntime.availableProcessors())
}
/**
* Reflectively provide a new instance of a class and configure it appropriately.
*/
- def newInstance[C <: AnyRef](_class: Class[C], className: String): C = {
+ def newInstance[C <: AnyRef](_class: Class[C]): C = {
+ newInstance(_class.getName)
+ }
+
+ /**
+ * Reflectively provide a new instance of a class and configure it appropriately.
+ */
+ def newInstance[C <: AnyRef](className: String): C = {
val originalProps = apply(EnvKeys.originalProps)
val instanceM = MaybeEither(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
"default"
}
+ def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = {
+ val className = resourceType.chargingBehavior
+ _chargingBehaviorMap.get(className) match {
+ case Some(chargingBehavior) ⇒
+ chargingBehavior
+
+ case _ ⇒
+ // It does not matter if this is entered by multiple threads and more than one instance of the same class
+ // is created. The returned instance is not meant to be cached.
+ val chargingBehavior = newInstance[ChargingBehavior](className)
+ _chargingBehaviorMap synchronized {
+ _chargingBehaviorMap = _chargingBehaviorMap.updated(className, chargingBehavior)
+ }
+
+ chargingBehavior
+ }
+ }
+
def defaultClassLoader = apply(EnvKeys.defaultClassLoader)
- def resourceEventStore = apply(EnvKeys.resourceEventStore)
+ def resourceEventStore = apply(EnvKeys.storeProvider).resourceEventStore
- def imEventStore = apply(EnvKeys.imEventStore)
+ def imEventStore = apply(EnvKeys.storeProvider).imEventStore
- def userStateStore = apply(EnvKeys.userStateStore)
+ def userStateStore = apply(EnvKeys.storeProvider).userStateStore
- def policyStore = apply(EnvKeys.policyStore)
+ def policyStore = apply(EnvKeys.storeProvider).policyStore
def eventsStoreFolder = apply(EnvKeys.eventsStoreFolder)
}
final class AquariumEnvKey[T: Manifest](override val name: String) extends TypedKeySkeleton[T](name) {
- override def toString = name
+ override def toString = "%s(%s)".format(manifest[T], name)
}
final val ServiceKeys: List[TypedKey[_ <: Lifecycle]] = List(
final val adminCookie: TypedKey[Option[String]] =
new AquariumEnvKey[Option[String]]("admin.cookie")
- final val resourceEventStore: TypedKey[ResourceEventStore] =
- new AquariumEnvKey[ResourceEventStore]("resource.event.store.class")
+// final val resourceEventStore: TypedKey[ResourceEventStore] =
+// new AquariumEnvKey[ResourceEventStore]("resource.event.store.class")
- final val imEventStore: TypedKey[IMEventStore] =
- new AquariumEnvKey[IMEventStore]("im.event.store.class")
+// final val imEventStore: TypedKey[IMEventStore] =
+// new AquariumEnvKey[IMEventStore]("im.event.store.class")
- final val userStateStore: TypedKey[UserStateStore] =
- new AquariumEnvKey[UserStateStore]("user.state.store.class")
+// final val userStateStore: TypedKey[UserStateStore] =
+// new AquariumEnvKey[UserStateStore]("user.state.store.class")
- final val policyStore: TypedKey[PolicyStore] =
- new AquariumEnvKey[PolicyStore]("policy.store.class")
+// final val policyStore: TypedKey[PolicyStore] =
+// new AquariumEnvKey[PolicyStore]("policy.store.class")
/**
* The class that initializes the REST service
import com.ckkloverdos.key.{BooleanKey, TypedKey}
import com.ckkloverdos.env.Env
import com.ckkloverdos.props.Props
-import com.ckkloverdos.maybe.{MaybeOption, Failed, MaybeEither, Just, NoVal}
+import com.ckkloverdos.maybe.{Failed, MaybeEither, Just, NoVal}
import gr.grnet.aquarium.util.Loggable
import java.io.File
-import gr.grnet.aquarium.store.StoreProvider
-import gr.grnet.aquarium.logic.accounting.algorithm.SimpleCostPolicyAlgorithmCompiler
import gr.grnet.aquarium.computation.UserStateComputations
import gr.grnet.aquarium.service.{StoreWatcherService, RabbitMQService, AkkaService, SimpleTimerService, EventBusService}
import gr.grnet.aquarium.converter.StdConverters
val propName = envKey.name
originalProps.get(propName) match {
- case Just(propValue) ⇒
- update(envKey, newInstance(envKey.keyType, propValue))
+ case Just(storeProviderClassName) ⇒
+ update(envKey, newInstance(envKey.keyType, storeProviderClassName))
case NoVal ⇒
throw new AquariumInternalError("No store provider is given in properties")
}
}
- private[this] def checkStoreOverrides: Unit = {
- if(originalProps eq null) {
- return
- }
-
- def checkOverride[S <: AnyRef : Manifest](envKey: TypedKey[S], f: StoreProvider ⇒ S): Unit = {
- if(!_env.contains(envKey)) {
- val propName = envKey.name
-
- originalProps.get(propName) match {
- case Just(propValue) ⇒
- // Create the store reflectively
- update(envKey, newInstance(envKey.keyType, propValue))
-
- case NoVal ⇒
- // Get the store from the store provider
- val storeProvider = this.envGetEx(EnvKeys.storeProvider)
- val propValue = f(storeProvider)
- update(envKey, propValue)
-
- case Failed(e) ⇒
- throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
- }
- }
- }
-
- // If a store has not been specifically overridden, we load it from the properties
- checkOverride(EnvKeys.resourceEventStore, _.resourceEventStore)
- checkOverride(EnvKeys.imEventStore, _.imEventStore)
- checkOverride(EnvKeys.userStateStore, _.userStateStore)
- checkOverride(EnvKeys.policyStore, _.policyStore)
- }
-
private[this] def checkEventsStoreFolderOverride: Unit = {
val propName = EnvKeys.eventsStoreFolder.name
checkNoPropsOverride(EnvKeys.converters) { _ ⇒ StdConverters.AllConverters }
checkStoreProviderOverride
- checkStoreOverrides
checkEventsStoreFolderOverride
checkEventsStoreFolderExistence
import ch.qos.logback.classic.joran.JoranConfigurator
import com.ckkloverdos.maybe.Just
import gr.grnet.aquarium.service.event.AquariumCreatedEvent
+import gr.grnet.aquarium.policy.PolicyModel
/**
* Main method for Aquarium
package gr.grnet.aquarium
-import com.ckkloverdos.maybe.{Failed, Just, Maybe, NoVal}
+import com.ckkloverdos.maybe.{Maybe, NoVal}
import com.ckkloverdos.sys.{SysEnv, SysProp}
import java.io.File
import gr.grnet.aquarium.util.justForSure
-import gr.grnet.aquarium.util.isRunningTests
import com.ckkloverdos.resource.{FileStreamResource, StreamResource, CompositeStreamResourceContext, ClassLoaderStreamResourceContext, FileStreamResourceContext}
import com.ckkloverdos.props.Props
-import com.ckkloverdos.convert.Converters._
import com.ckkloverdos.maybe.Just
import com.ckkloverdos.maybe.Failed
import com.ckkloverdos.convert.Converters
/**
* Locates resources.
*
- * This code was initially in [[gr.grnet.aquarium.Aquarium]].
- *
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
final val LOGBACK_XML = "logback.xml"
final val AQUARIUM_PROPERTIES = "aquarium.properties"
- final val POLICY_YAML = "policy.yaml"
- final val ROLE_AGREEMENTS_MAP = "roles-agreements.map"
+ final val POLICY_JSON = "policy.json"
}
final object Homes {
}
}
- final lazy val PolicyYAMLResource = {
- ResourceLocator.getResource(ResourceLocator.ResourceNames.POLICY_YAML) match {
- case Just(policyYAML) ⇒
- policyYAML
+ final lazy val PolicyJSONResource = {
+ ResourceLocator.getResource(ResourceLocator.ResourceNames.POLICY_JSON) match {
+ case Just(policyJSON) ⇒
+ policyJSON
case NoVal ⇒
throw new AquariumInternalError(
- "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_YAML))
+ "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_JSON))
case Failed(e) ⇒
throw new AquariumInternalError(
- "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_YAML), e)
+ "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_JSON), e)
}
}
}
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-abstract class ChargingBehavior(val name: String, val inputs: Set[ChargingInput]) {
+abstract class ChargingBehavior(val alias: String, val inputs: Set[ChargingInput]) {
final lazy val inputNames = inputs.map(_.name)
unitPrice)
}
- def isNamed(aName: String): Boolean = aName == name
+ def isNamed(aName: String): Boolean = aName == alias
def needsPreviousEventForCreditAndAmountCalculation: Boolean = {
// If we need any variable that is related to the previous event
}
object ChargingBehavior {
- def apply(name: String): ChargingBehavior = {
- name match {
- case null ⇒
- throw new AquariumException("<null> charging behavior")
-
- case name ⇒ name.toLowerCase match {
- case ChargingBehaviorNames.onoff ⇒ OnOffChargingBehavior
- case ChargingBehaviorNames.discrete ⇒ DiscreteChargingBehavior
- case ChargingBehaviorNames.continuous ⇒ ContinuousChargingBehavior
- case ChargingBehaviorNames.once ⇒ ContinuousChargingBehavior
-
- case _ ⇒
- throw new AquariumException("Invalid charging behavior %s".format(name))
- }
- }
- }
-
def makeValueMapFor(
chargingBehavior: ChargingBehavior,
totalCredits: Double,
val inputs = chargingBehavior.inputs
var map = Map[ChargingInput, Any]()
- if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.name
+ if(inputs contains ChargingBehaviorNameInput) map += ChargingBehaviorNameInput -> chargingBehavior.alias
if(inputs contains TotalCreditsInput ) map += TotalCreditsInput -> totalCredits
if(inputs contains OldTotalAmountInput) map += OldTotalAmountInput -> oldTotalAmount
if(inputs contains NewTotalAmountInput) map += NewTotalAmountInput -> newTotalAmount
map
}
}
-
-/**
- * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
- *
- * Examples are: a) Give a gift of X credits to the user, b) User bought a book, so charge for the book price.
- *
- */
-case object OnceChargingBehavior
-extends ChargingBehavior(
- ChargingBehaviorNames.once,
- Set(ChargingBehaviorNameInput, CurrentValueInput)
-) {
-
- /**
- * This is called when we have the very first event for a particular resource instance, and we want to know
- * if it is billable or not.
- */
- def isBillableFirstEvent(event: ResourceEventModel) = {
- true
- }
-
- def mustGenerateDummyFirstEvent = false // no need to
-
- def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
- oldAmount
- }
-
- def getResourceInstanceInitialAmount = 0.0
-
- def supportsImplicitEvents = false
-
- def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
-
- def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
- throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
- }
-}
-
-/**
- * In practice a resource usage will be charged for the total amount of usage
- * between resource usage changes.
- *
- * Example resource that might be adept to a continuous policy
- * is diskspace.
- */
-case object ContinuousChargingBehavior
-extends ChargingBehavior(
- ChargingBehaviorNames.continuous,
- Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)
-) {
-
- def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
- // If the total is in the details, get it, or else compute it
- details.get("total") match {
- case Some(total) ⇒
- total.toDouble
-
- case _ ⇒
- oldAmount + newEventValue
- }
- }
-
- def getResourceInstanceInitialAmount: Double = {
- 0.0
- }
-
- /**
- * This is called when we have the very first event for a particular resource instance, and we want to know
- * if it is billable or not.
- */
- def isBillableFirstEvent(event: ResourceEventModel) = {
- true
- }
-
- def mustGenerateDummyFirstEvent = true
-
- def supportsImplicitEvents = {
- true
- }
-
- def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
- true
- }
-
- def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
- assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
-
- val details = resourceEvent.details
- val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
-
- resourceEvent.withDetails(newDetails, newOccurredMillis)
- }
-}
-
-/**
- * An onoff charging behavior expects a resource to be in one of the two allowed
- * states (`on` and `off`, respectively). It will charge for resource usage
- * within the timeframes specified by consecutive on and off resource events.
- * An onoff policy is the same as a continuous policy, except for
- * the timeframes within the resource is in the `off` state.
- *
- * Example resources that might be adept to onoff policies are VMs in a
- * cloud application and books in a book lending application.
- */
-case object OnOffChargingBehavior
-extends ChargingBehavior(
- ChargingBehaviorNames.onoff,
- Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput)
-) {
-
- /**
- *
- * @param oldAmount is ignored
- * @param newEventValue
- * @return
- */
- def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
- newEventValue
- }
-
- def getResourceInstanceInitialAmount: Double = {
- 0.0
- }
-
- private[this]
- def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
- import OnOffChargingBehaviorValues.{ON, OFF}
-
- def exception(rs: OnOffPolicyResourceState) =
- new AquariumException("Resource state transition error (%s -> %s)".format(rs, rs))
-
- (oldAmount, newEventValue) match {
- case (ON, ON) ⇒
- throw exception(OnResourceState)
- case (ON, OFF) ⇒
- OFF
- case (OFF, ON) ⇒
- ON
- case (OFF, OFF) ⇒
- throw exception(OffResourceState)
- }
- }
-
- override def isBillableEvent(event: ResourceEventModel) = {
- // ON events do not contribute, only OFF ones.
- OnOffChargingBehaviorValues.isOFFValue(event.value)
- }
-
- /**
- * This is called when we have the very first event for a particular resource instance, and we want to know
- * if it is billable or not.
- */
- def isBillableFirstEvent(event: ResourceEventModel) = {
- false
- }
-
- def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs
-
- def supportsImplicitEvents = {
- true
- }
-
- def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
- // If we have ON events with no OFF companions at the end of the billing period,
- // then we must generate implicit OFF events.
- OnOffChargingBehaviorValues.isONValue(resourceEvent.value)
- }
-
- def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
- assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
- assert(OnOffChargingBehaviorValues.isONValue(resourceEvent.value))
-
- val details = resourceEvent.details
- val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
- val newValue = OnOffChargingBehaviorValues.OFF
-
- resourceEvent.withDetailsAndValue(newDetails, newValue, newOccurredMillis)
- }
-
- def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = {
- throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this))
- }
-}
-
-object OnOffChargingBehaviorValues {
- final val ON = 1.0
- final val OFF = 0.0
-
- def isONValue (value: Double) = value == ON
- def isOFFValue(value: Double) = value == OFF
-}
-
-/**
- * An discrete charging behavior indicates that a resource should be charged directly
- * at each resource state change, i.e. the charging is not dependent on
- * the time the resource.
- *
- * Example oneoff resources might be individual charges applied to various
- * actions (e.g. the fact that a user has created an account) or resources
- * that should be charged per volume once (e.g. the allocation of a volume)
- */
-case object DiscreteChargingBehavior
-extends ChargingBehavior(
- ChargingBehaviorNames.discrete,
- Set(ChargingBehaviorNameInput, UnitPriceInput, CurrentValueInput)
-) {
-
- def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
- oldAmount + newEventValue
- }
-
- def getResourceInstanceInitialAmount: Double = {
- 0.0
- }
-
- /**
- * This is called when we have the very first event for a particular resource instance, and we want to know
- * if it is billable or not.
- */
- def isBillableFirstEvent(event: ResourceEventModel) = {
- false // nope, we definitely need a previous one.
- }
-
- // FIXME: Check semantics of this. I just put false until thorough study
- def mustGenerateDummyFirstEvent = false
-
- def supportsImplicitEvents = {
- false
- }
-
- def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
- false
- }
-
- def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
- throw new AquariumInternalError("constructImplicitEndEventFor() Not compliant with %s".format(this))
- }
-}
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-object ChargingBehaviorNames {
+object ChargingBehaviorAliases {
final val onoff = "onoff"
final val discrete = "discrete"
final val continuous = "continuous"
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+
+/**
+ * In practice a resource usage will be charged for the total amount of usage
+ * between resource usage changes.
+ *
+ * Example resource that might be adept to a continuous policy
+ * is diskspace.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class ContinuousChargingBehavior
+ extends ChargingBehavior(
+ ChargingBehaviorAliases.continuous,
+ Set(ChargingBehaviorNameInput, UnitPriceInput, OldTotalAmountInput, TimeDeltaInput)) {
+
+ def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+ // If the total is in the details, get it, or else compute it
+ details.get("total") match {
+ case Some(total) ⇒
+ total.toDouble
+
+ case _ ⇒
+ oldAmount + newEventValue
+ }
+ }
+
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
+ }
+
+ /**
+ * This is called when we have the very first event for a particular resource instance, and we want to know
+ * if it is billable or not.
+ */
+ def isBillableFirstEvent(event: ResourceEventModel) = {
+ true
+ }
+
+ def mustGenerateDummyFirstEvent = true
+
+ def supportsImplicitEvents = {
+ true
+ }
+
+ def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+ true
+ }
+
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
+ assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
+
+ val details = resourceEvent.details
+ val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
+
+ resourceEvent.withDetails(newDetails, newOccurredMillis)
+ }
+}
+
+object ContinuousChargingBehavior {
+ private[this] final val TheOne = new ContinuousChargingBehavior
+
+ def apply(): ContinuousChargingBehavior = TheOne
+}
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.AquariumInternalError
+
+/**
+ * An discrete charging behavior indicates that a resource should be charged directly
+ * at each resource state change, i.e. the charging is not dependent on
+ * the time the resource.
+ *
+ * Example oneoff resources might be individual charges applied to various
+ * actions (e.g. the fact that a user has created an account) or resources
+ * that should be charged per volume once (e.g. the allocation of a volume)
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class DiscreteChargingBehavior
+ extends ChargingBehavior(
+ ChargingBehaviorAliases.discrete,
+ Set(ChargingBehaviorNameInput, UnitPriceInput, CurrentValueInput)) {
+
+ def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+ oldAmount + newEventValue
+ }
+
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
+ }
+
+ /**
+ * This is called when we have the very first event for a particular resource instance, and we want to know
+ * if it is billable or not.
+ */
+ def isBillableFirstEvent(event: ResourceEventModel) = {
+ false // nope, we definitely need a previous one.
+ }
+
+ // FIXME: Check semantics of this. I just put false until thorough study
+ def mustGenerateDummyFirstEvent = false
+
+ def supportsImplicitEvents = {
+ false
+ }
+
+ def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+ false
+ }
+
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
+ throw new AquariumInternalError("constructImplicitEndEventFor() Not compliant with %s".format(this))
+ }
+}
+
+object DiscreteChargingBehavior {
+ private[this] final val TheOne = new DiscreteChargingBehavior
+
+ def apply(): DiscreteChargingBehavior = TheOne
+}
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging
+
+import gr.grnet.aquarium.{AquariumInternalError, AquariumException}
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+
+/**
+ * An onoff charging behavior expects a resource to be in one of the two allowed
+ * states (`on` and `off`, respectively). It will charge for resource usage
+ * within the timeframes specified by consecutive on and off resource events.
+ * An onoff policy is the same as a continuous policy, except for
+ * the timeframes within the resource is in the `off` state.
+ *
+ * Example resources that might be adept to onoff policies are VMs in a
+ * cloud application and books in a book lending application.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class OnOffChargingBehavior
+ extends ChargingBehavior(
+ ChargingBehaviorAliases.onoff,
+ Set(ChargingBehaviorNameInput, UnitPriceInput, TimeDeltaInput)) {
+
+ /**
+ *
+ * @param oldAmount is ignored
+ * @param newEventValue
+ * @return
+ */
+ def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]): Double = {
+ newEventValue
+ }
+
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
+ }
+
+ private[this]
+ def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
+ import OnOffChargingBehaviorValues.{ON, OFF}
+
+ def exception(rs: OnOffPolicyResourceState) =
+ new AquariumException("Resource state transition error (%s -> %s)".format(rs, rs))
+
+ (oldAmount, newEventValue) match {
+ case (ON, ON) ⇒
+ throw exception(OnResourceState)
+ case (ON, OFF) ⇒
+ OFF
+ case (OFF, ON) ⇒
+ ON
+ case (OFF, OFF) ⇒
+ throw exception(OffResourceState)
+ }
+ }
+
+ override def isBillableEvent(event: ResourceEventModel) = {
+ // ON events do not contribute, only OFF ones.
+ OnOffChargingBehaviorValues.isOFFValue(event.value)
+ }
+
+ /**
+ * This is called when we have the very first event for a particular resource instance, and we want to know
+ * if it is billable or not.
+ */
+ def isBillableFirstEvent(event: ResourceEventModel) = {
+ false
+ }
+
+ def mustGenerateDummyFirstEvent = false // should be handled by the implicit OFFs
+
+ def supportsImplicitEvents = {
+ true
+ }
+
+ def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = {
+ // If we have ON events with no OFF companions at the end of the billing period,
+ // then we must generate implicit OFF events.
+ OnOffChargingBehaviorValues.isONValue(resourceEvent.value)
+ }
+
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
+ assert(supportsImplicitEvents && mustConstructImplicitEndEventFor(resourceEvent))
+ assert(OnOffChargingBehaviorValues.isONValue(resourceEvent.value))
+
+ val details = resourceEvent.details
+ val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
+ val newValue = OnOffChargingBehaviorValues.OFF
+
+ resourceEvent.withDetailsAndValue(newDetails, newValue, newOccurredMillis)
+ }
+
+ def constructImplicitStartEventFor(resourceEvent: ResourceEventModel) = {
+ throw new AquariumInternalError("constructImplicitStartEventFor() Not compliant with %s".format(this))
+ }
+}
+
+object OnOffChargingBehavior {
+ private[this] final val TheOne = new OnOffChargingBehavior
+
+ def apply(): OnOffChargingBehavior = TheOne
+}
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-object OnOffPolicyResourceStateNames {
- final val on = "on"
- final val off = "off"
+object OnOffChargingBehaviorValues {
+ final val ON = 1.0
+ final val OFF = 0.0
+
+ def isONValue (value: Double) = value == ON
+ def isOFFValue(value: Double) = value == OFF
}
+
def isOff: Boolean = !isOn
}
-object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.on) {
+object OnResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.on) {
override def isOn = true
}
-object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceStateNames.off) {
+object OffResourceState extends OnOffPolicyResourceState(OnOffPolicyResourceState.Names.off) {
override def isOff = true
}
object OnOffPolicyResourceState {
- def apply(name: Any): OnOffPolicyResourceState = {
- name match {
- case x: String if (x.equalsIgnoreCase(OnOffPolicyResourceStateNames.on)) ⇒ OnResourceState
- case y: String if (y.equalsIgnoreCase(OnOffPolicyResourceStateNames.off)) ⇒ OffResourceState
- case a: Double if (a == 0) => OffResourceState
- case b: Double if (b == 1) => OnResourceState
- case i: Int if (i == 0) => OffResourceState
- case j: Int if (j == 1) => OnResourceState
- case _ => throw new AquariumException("Invalid OnOffPolicyResourceState %s".format(name))
- }
+ object Names {
+ final val on = "on"
+ final val off = "off"
}
}
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.charging
+
+import gr.grnet.aquarium.event.model.resource.ResourceEventModel
+import gr.grnet.aquarium.AquariumException
+
+/**
+ * A charging behavior for which resource events just carry a credit amount that will be added to the total one.
+ *
+ * Examples are: a) Give a gift of X credits to the user, b) User bought a book, so charge for the book price.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+final class OnceChargingBehavior
+ extends ChargingBehavior(
+ ChargingBehaviorAliases.once,
+ Set(ChargingBehaviorNameInput, CurrentValueInput)) {
+
+ /**
+ * This is called when we have the very first event for a particular resource instance, and we want to know
+ * if it is billable or not.
+ */
+ def isBillableFirstEvent(event: ResourceEventModel) = {
+ true
+ }
+
+ def mustGenerateDummyFirstEvent = false // no need to
+
+ def computeNewAccumulatingAmount(oldAmount: Double, newEventValue: Double, details: Map[String, String]) = {
+ oldAmount
+ }
+
+ def getResourceInstanceInitialAmount = 0.0
+
+ def supportsImplicitEvents = false
+
+ def mustConstructImplicitEndEventFor(resourceEvent: ResourceEventModel) = false
+
+ def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, occurredMillis: Long) = {
+ throw new AquariumException("constructImplicitEndEventFor() Not compliant with %s".format(this))
+ }
+}
+
+object OnceChargingBehavior {
+ private[this] final val TheOne = new OnceChargingBehavior
+
+ def apply(): OnceChargingBehavior = TheOne
+}
import gr.grnet.aquarium.util.{ContextualLogger, Loggable}
import gr.grnet.aquarium.store.PolicyStore
import gr.grnet.aquarium.util.date.MutableDateCalc
-import gr.grnet.aquarium.AquariumInternalError
+import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
import gr.grnet.aquarium.logic.accounting.algorithm.SimpleExecutableChargingBehaviorAlgorithm
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
*
*/
def computeFullChargeslots(
+ aquarium: Aquarium,
previousResourceEventOpt: Option[ResourceEventModel],
currentResourceEvent: ResourceEventModel,
oldCredits: Double,
val occurredDate = currentResourceEvent.occurredDate
val occurredMillis = currentResourceEvent.occurredMillis
- val chargingBehavior = resourceType.chargingBehavior
+ val chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
val (referenceTimeslot, policyByTimeslot, previousValue) = chargingBehavior.needsPreviousEventForCreditAndAmountCalculation match {
// We need a previous event
resourceTypesMap.get(theResource) match {
// We have a resource type (and thus a charging behavior)
case Some(resourceType) ⇒
- val chargingBehavior = resourceType.chargingBehavior
+ val chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
clog.debug("%s for %s", chargingBehavior, resourceType)
val isBillable = chargingBehavior.isBillableEvent(currentResourceEvent)
if(!isBillable) {
// clog.debug("Computing full chargeslots")
val (referenceTimeslot, fullChargeslots) = timeslotComputations.computeFullChargeslots(
+ aquarium,
previousResourceEventOpt1,
currentResourceEvent,
oldCredits,
// Second, for the remaining events which must contribute an implicit OFF, we collect those OFFs
// ... in order to generate an implicit ON later (during the next billing cycle).
val (specialEvents, theirImplicitEnds) = userStateWorker.
- findAndRemoveGeneratorsOfImplicitEndEvents(billingMonthInfo.monthStopMillis)
+ findAndRemoveGeneratorsOfImplicitEndEvents(aquarium, billingMonthInfo.monthStopMillis)
if(specialEvents.lengthCompare(1) >= 0 || theirImplicitEnds.lengthCompare(1) >= 0) {
clog.debug("")
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
import gr.grnet.aquarium.computation.state.parts.{IgnoredFirstResourceEventsWorker, ImplicitlyIssuedResourceEventsWorker, LatestResourceEventsWorker}
import gr.grnet.aquarium.policy.ResourceType
+import gr.grnet.aquarium.Aquarium
/**
* A helper object holding intermediate state/results during resource event processing.
* @see [[gr.grnet.aquarium.charging.ChargingBehavior]]
*/
def findAndRemoveGeneratorsOfImplicitEndEvents(
+ aquarium: Aquarium,
/**
* The `occurredMillis` that will be recorded in the synthetic implicit OFFs.
* Normally, this will be the end of a billing month.
for {
resourceEvent ← resourceEvents
resourceType ← resourceTypesMap.get(resourceEvent.safeResource)
- chargingBehavior = resourceType.chargingBehavior
+ chargingBehavior = aquarium.chargingBehaviorOf(resourceType)
} {
if(chargingBehavior.supportsImplicitEvents) {
if(chargingBehavior.mustConstructImplicitEndEventFor(resourceEvent)) {
package gr.grnet.aquarium.logic.accounting.algorithm
-import com.ckkloverdos.maybe.Maybe
-import gr.grnet.aquarium.logic.accounting.dsl._
import gr.grnet.aquarium.AquariumException
-import gr.grnet.aquarium.charging.{CurrentValueInput, TimeDeltaInput, OldTotalAmountInput, UnitPriceInput, ChargingBehaviorNames, ChargingBehaviorNameInput, ChargingInput}
+import gr.grnet.aquarium.charging.{CurrentValueInput, TimeDeltaInput, OldTotalAmountInput, UnitPriceInput, ChargingBehaviorAliases, ChargingBehaviorNameInput, ChargingInput}
/**
* An executable charging algorithm with some simple implementation.
def apply(vars: Map[ChargingInput, Any]): Double = {
vars.apply(ChargingBehaviorNameInput) match {
- case ChargingBehaviorNames.continuous ⇒
+ case ChargingBehaviorAliases.continuous ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
hrs(timeDelta) * oldTotalAmount * unitPrice
- case ChargingBehaviorNames.discrete ⇒
+ case ChargingBehaviorAliases.discrete ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
currentValue * unitPrice
- case ChargingBehaviorNames.onoff ⇒
+ case ChargingBehaviorAliases.onoff ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
hrs(timeDelta) * unitPrice
- case ChargingBehaviorNames.once ⇒
+ case ChargingBehaviorAliases.once ⇒
val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
currentValue
override def toString = "SimpleExecutableCostPolicyAlgorithm(%s)".format(
Map(
- ChargingBehaviorNames.continuous -> "hrs(timeDelta) * oldTotalAmount * unitPrice",
- ChargingBehaviorNames.discrete -> "currentValue * unitPrice",
- ChargingBehaviorNames.onoff -> "hrs(timeDelta) * unitPrice",
- ChargingBehaviorNames.once -> "currentValue"))
+ ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * unitPrice",
+ ChargingBehaviorAliases.discrete -> "currentValue * unitPrice",
+ ChargingBehaviorAliases.onoff -> "hrs(timeDelta) * unitPrice",
+ ChargingBehaviorAliases.once -> "currentValue"))
}
*/
private[dsl] def includes(t: Date) : Boolean = start <= t.getTime && t.getTime <= end
- private[dsl] def weakIncludes(t: Date) : Boolean = start < t.getTime && t.getTime < end
/**
* Check whether this timeslot overlaps with the provided one.
def overlaps(t: Timeslot) : Boolean =
contains(t) || t.contains(this) || this.includes(t.from) || this.includes(t.to)
- def weakOverlaps(t: Timeslot) : Boolean =
- contains(t) || t.contains(this) || this.weakIncludes(t.from) || this.weakIncludes(t.to)
/**
* Merges this timeslot with the provided one. If the timeslots overlap,
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
import collection.mutable
-import java.util
/**
*
false
case Some(d_end) =>
result += Timeslot(d_start,d_end)
- offset = d_end //new util.Date(d_end.getTime + 1000L)
+ offset = d_end
d_end.before(t.to)
}
}) ()
case None => "? ? ? ? ?"
case Some((_,s)) => s.toString
}
- override def toString : String = "EffectiveUnitPrice(%d,%s,%s)".
+ override def toString : String = "EffectiveUnitPrice(%f,%s,%s)".
format(unitPrice,stringOfStartCron,stringOfEndCron)
}
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.policy
+
+import scala.collection.immutable.TreeSet
+import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.util.Lock
+
+/**
+ * A mutable container of policy models.
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+class PolicyHistory {
+ private[this] val lock = new Lock()
+ @volatile private[this] var _policies = TreeSet[PolicyModel]()
+
+ def insertNewPolicy(newPolicy: PolicyModel): Unit = {
+ lock.withLock(_policies += newPolicy)
+ }
+
+ /**
+ * Return the last (ordered) policy that starts before timeMillis
+ *
+ * @param timeMillis
+ * @return
+ */
+ def findPolicyAt(timeMillis: Long): Option[PolicyModel] = {
+ lock.withLock {
+ // Take the subset of all ordered policies up to the one with less than or equal start time
+ // and then return the last item. This should be the policy right before the given time.
+ // TODO: optimize the creation of the fake StdPolicy
+ _policies.to(new StdPolicy("", None, Timespan(timeMillis), Set(), Set(), Map())).lastOption
+ }
+ }
+}
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-trait PolicyModel extends JsonSupport {
+trait PolicyModel extends Ordered[PolicyModel] with JsonSupport {
+ final def compare(that: PolicyModel): Int = {
+ if(this.validFrom < that.validFrom) {
+ -1
+ } else if(this.validFrom == that.validFrom) {
+ 0
+ } else {
+ 1
+ }
+ }
+
def id: String
def parentID: Option[String]
def idInStore: Option[Any]
-
/**
* The time period within which this policy is valid.
*/
* Note than since a charging behavior is semantically attached to an implementation, a change in the set
* of known charging behaviors normally means a change in the implementation of Aquarium.
*/
- def chargingBehaviorClasses: Set[String/*ImplementationClassName*/]
+ def chargingBehaviors: Set[String/*ImplementationClassName*/]
/**
* Each role is mapped to a full price table.
package gr.grnet.aquarium.policy
-import gr.grnet.aquarium.charging.ChargingBehavior
/**
* The definition of a resource type. A resource type is a broad classification to which resource instances belong.
*
* @param name The name of this resource type. Examples are `vmtime`, `diskspace`, `bandwidth`, `diskio`.
* @param unit The unit we use to count the resource usage.
- * @param chargingBehavior The charging behavior used for this resource types.
+ * @param chargingBehavior The fully qualified name of the [[gr.grnet.aquarium.charging.ChargingBehavior]]
+ * implementation used for this resource type.
*
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-case class ResourceType(name: String, unit: String, chargingBehavior: ChargingBehavior)
+case class ResourceType(name: String, unit: String, chargingBehavior: String)
parentID: Option[String],
validityTimespan: Timespan,
resourceTypes: Set[ResourceType],
- chargingBehaviorClasses: Set[String],
+ chargingBehaviors: Set[String],
roleMapping: Map[String/*Role*/, FullPriceTable]
) extends PolicyModel {
// Create new User Actor instance
logger.debug("Creating new UserActor instance for %s".format(userID))
val actorRef = _actorSystem.actorOf(Props.apply({
- aquarium.newInstance(classOf[UserActor], classOf[UserActor].getName)
+ aquarium.newInstance(classOf[UserActor])
}), "userActor::%s".format(userID))
// Cache it for subsequent calls
stringResponseOK("%s\n%s\n%s\n" .format(
ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES,
ResourceLocator.ResourceNames.LOGBACK_XML,
- ResourceLocator.ResourceNames.POLICY_YAML),
+ ResourceLocator.ResourceNames.POLICY_JSON),
TEXT_PLAIN)
case RESTPaths.ResourcesAquariumPropertiesPath() ⇒
case RESTPaths.ResourcesLogbackXMLPath() ⇒
resourceInfoResponse(ResourceLocator.Resources.LogbackXMLResource, TEXT_PLAIN)
- case RESTPaths.ResourcesPolicyYAMLPath() ⇒
- resourceInfoResponse(ResourceLocator.Resources.PolicyYAMLResource, TEXT_PLAIN)
+ case RESTPaths.ResourcesPolicyJSONPath() ⇒
+ resourceInfoResponse(ResourceLocator.Resources.PolicyJSONResource, TEXT_PLAIN)
}
val EventsHandler: URIPF = {
final val ResourcesLogbackXMLPath = toResourcesPath(ResourceLocator.ResourceNames.LOGBACK_XML).r
- final val ResourcesPolicyYAMLPath = toResourcesPath(ResourceLocator.ResourceNames.POLICY_YAML).r
+ final val ResourcesPolicyJSONPath = toResourcesPath(ResourceLocator.ResourceNames.POLICY_JSON).r
final val ResourceEventPath = toEventPath("rcevent").r
val tag = Tags.ResourceEventTag
logger.info("Scheduling ping for %s store".format(tag))
+ logger.info("AQUARIUM = {}", aquarium)
+ logger.info("AQUARIUM.resourceEventStore = {}", aquarium.resourceEventStore)
doSchedulePing(
tag,
* @author Christos KK Loverdos <loverdos@gmail.com>
*/
-class ResourceSim(val name: String, val unit: String, val chargingBehavior: ChargingBehavior ) {
+class ResourceSim(val name: String, val unit: String, val chargingBehavior: String) {
def toResourceType = ResourceType(name, unit, chargingBehavior)
object ResourceSim {
def apply(name: String, unit: String, chargingBehavior: ChargingBehavior) = {
- new ResourceSim(name, unit, chargingBehavior)
+ new ResourceSim(name, unit, chargingBehavior.getClass.getName)
}
}
class StdBandwidthResourceSim(
name: String = StdVMTimeResourceSim.DSLNames.name,
unit: String = StdVMTimeResourceSim.DSLNames.unit,
- costPolicy: ChargingBehavior = DiscreteChargingBehavior
-) extends ResourceSim(name, unit, costPolicy) {
+ chargingBehavior: String = classOf[DiscreteChargingBehavior].getName
+) extends ResourceSim(name, unit, chargingBehavior) {
override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
StdBandwidthInstanceSim(this, instanceId, owner, client)
class StdDiskspaceResourceSim(
name: String = StdVMTimeResourceSim.DSLNames.name,
unit: String = StdVMTimeResourceSim.DSLNames.unit,
- chargingBehavior: ChargingBehavior = ContinuousChargingBehavior
+ chargingBehavior: String = classOf[ContinuousChargingBehavior].getName
) extends ResourceSim(name, unit, chargingBehavior) {
override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
class StdVMTimeResourceSim(
name: String = StdVMTimeResourceSim.DSLNames.name,
unit: String = StdVMTimeResourceSim.DSLNames.unit,
- chargingBehavior: ChargingBehavior = OnOffChargingBehavior
+ chargingBehavior: String = classOf[OnOffChargingBehavior].getName
) extends ResourceSim(name, unit, chargingBehavior) {
override def newInstance(instanceId: String, owner: UserSim, client: ClientSim) =
parentID = policy.parentID,
validityTimespan = policy.validityTimespan,
resourceTypes = policy.resourceTypes,
- chargingBehaviorClasses = policy.chargingBehaviorClasses,
+ chargingBehaviors = policy.chargingBehaviors,
roleMapping = policy.roleMapping
)
_policies = localPolicy :: _policies
case class MongoDBPolicy(
_id: String,
- id: String,
parentID: Option[String],
validityTimespan: Timespan,
resourceTypes: Set[ResourceType],
- chargingBehaviorClasses: Set[String],
+ chargingBehaviors: Set[String],
roleMapping: Map[String/*Role*/, FullPriceTable]
) extends PolicyModel {
+ def id = _id
def idInStore = Some(_id)
}
final def fromOther(policy: PolicyModel, _id: String): MongoDBPolicy = {
MongoDBPolicy(
_id,
- policy.id,
policy.parentID,
policy.validityTimespan,
policy.resourceTypes,
- policy.chargingBehaviorClasses,
+ policy.chargingBehaviors,
policy.roleMapping
)
}
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.util
+
+import java.util.concurrent.locks.ReentrantLock
+
+/**
+ * A wrapper around [[java.util.concurrent.locks.ReentrantLock]]
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+final class Lock(isFair: Boolean = false) {
+ private[this] val lock = new ReentrantLock(isFair)
+
+ def withLock[A](f: ⇒ A): A = {
+ lock.lock()
+ try f finally lock.unlock()
+ }
+}
# Relative to AQUARIUM_HOME or an absolute path
# DO NOT set this in production
-#events.store.folder=../events-store
+events.store.folder=../events-store
# Store resource events to events.store.folder as well
events.store.save.rc.events=false
rest.shutdown.timeout.millis=2000
# Store subsystem
store.provider.class=gr.grnet.aquarium.store.mongodb.MongoDBStoreProvider
-# Override the user store (if present, it will not be given by the store provider above)
-#user.state.store.class=gr.grnet.aquarium.store.memory.MemStorede the event store (if present, it will not be given by the store provider above)
-#resource.event.store.class=
-# Override the user event store (if present, it will not be given by the store provider above)
-#user.event.store.class=
-# Override the user event store (if present, it will not be given by the store provider above)
-#policy.store.class=
+
+# A time period in milliseconds for which we can tolerate stale parts regarding user state.
+user.state.timestamp.threshold=1
# Administrative REST API authorization cookie
admin.cookie=1
\ No newline at end of file
--- /dev/null
+{
+ "id":"3F8A9777-8C12-4529-B8E4-256AD840BEF2",
+
+ "validityTimespan":{
+ "fromMillis":0,
+ "toMillis":9223372036854775807
+ },
+
+ "resourceTypes":[
+ {
+ "name":"bandwidth",
+ "unit":"MB/Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.DiscreteChargingBehavior"
+ },
+ {
+ "name":"vmtime",
+ "unit":"Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.OnOffChargingBehavior"
+ },
+ {
+ "name":"diskspace",
+ "unit":"MB/Hr",
+ "chargingBehavior":"gr.grnet.aquarium.charging.ContinuousChargingBehavior"
+ }
+ ],
+
+ "chargingBehaviors":[
+ "gr.grnet.aquarium.charging.DiscreteChargingBehavior",
+ "gr.grnet.aquarium.charging.OnOffChargingBehavior",
+ "gr.grnet.aquarium.charging.ContinuousChargingBehavior",
+ "gr.grnet.aquarium.charging.OnceChargingBehavior"
+ ],
+
+ "roleMapping":{
+ "default":{
+ "perResource":{
+ "bandwidth":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ },
+ "vmtime":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ },
+ "diskspace":{
+ "priceOverrides":[
+ {
+ "unitPrice":0.01
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
import org.junit.Test
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.util.TestMethods
import java.util.Date
import scala._
import scala.Some
package gr.grnet.aquarium.logic.test
-import gr.grnet.aquarium.util.TestMethods
import org.junit.Assert._
import org.junit.{Test}
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
*
* @author Georgios Gousios <gousiosg@gmail.com>
*/
-class TimeslotTest extends TestMethods {
+class TimeslotTest /*extends TestMethods*/ {
@Test
def testOverlappingTimeslots = {
--- /dev/null
+/*
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and
+ * documentation are those of the authors and should not be
+ * interpreted as representing official policies, either expressed
+ * or implied, of GRNET S.A.
+ */
+
+package gr.grnet.aquarium.policy
+
+import org.junit.Test
+import gr.grnet.aquarium.Timespan
+import gr.grnet.aquarium.charging.{OnceChargingBehavior, ContinuousChargingBehavior, OnOffChargingBehavior, DiscreteChargingBehavior}
+import gr.grnet.aquarium.converter.{StdConverters, PrettyJsonTextFormat}
+
+/**
+ *
+ * @author Christos KK Loverdos <loverdos@gmail.com>
+ */
+
+class StdPolicyTest {
+ final lazy val policy = StdPolicy(
+ id = "policy-1",
+ parentID = None,
+
+ validityTimespan = Timespan(0),
+
+ resourceTypes = Set(
+ ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+ ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+ ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
+ ),
+
+ chargingBehaviors = Set(
+ classOf[DiscreteChargingBehavior].getName,
+ classOf[OnOffChargingBehavior].getName,
+ classOf[ContinuousChargingBehavior].getName,
+ classOf[OnceChargingBehavior].getName
+ ),
+
+ roleMapping = Map(
+ "default" -> FullPriceTable(Map(
+ "bandwidth" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+ "vmtime" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil),
+ "diskspace" -> EffectivePriceTable(EffectiveUnitPrice(0.01, None) :: Nil)
+ ))
+ )
+ )
+
+ @Test
+ def testJson(): Unit = {
+ val converters = StdConverters.AllConverters
+ val json = converters.convertEx[PrettyJsonTextFormat](policy)
+ val obj = converters.convertEx[StdPolicy](json)
+
+ assert(policy == obj)
+ }
+}
+++ /dev/null
-package gr.grnet.aquarium.policy.test
-
-import gr.grnet.aquarium.util.TestMethods
-import org.junit.Test
-import java.util
-import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
-import gr.grnet.aquarium.policy.{EffectiveUnitPrice, CronSpec}
-
-/*
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- * copyright notice, this list of conditions and the following
- * disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- */
-
-/**
- * Tests for the Timeslot class
- *
- * @author Prodromos Gerakios <pgerakios@grnet.gr>
- */
-class EffectiveUnitPriceTest extends TestMethods {
-
- private type EFU = EffectiveUnitPrice
- private type Intervals = List[Timeslot]
-
- private val printScreen = false
-
- private def noOverlap(i1:Intervals,i2:Intervals) = {
- for { t1 <- i1
- t2 <- i2
- } assert(!t1.weakOverlaps(t2),"Intervals\n t1="+t1 + " \nand\n t2="+t2 + "\n overlap.")
- }
-
- private def singleT(t:Timeslot,i1:Intervals,i2:Intervals) = {
- var l = Timeslot.mergeOverlaps(i1++i2)
- assert(l.size == 1)
- assert(l.head == t)
-
- }
- private def cronOK(cs:CronSpec,d:util.Date) = {
- assert(cs.includes(d))
- }
-
- private def cronNotOK(cs:CronSpec,d:util.Date) = {
- assert(!cs.includes(d))
- }
- private def testEFU(start:Long,end:Long,v:Double,cronStart:String,cronEnd:String) :
- (Intervals,Intervals) = {
- var cronStart0 : CronSpec = null
- var cronEnd0 : CronSpec = null
- val opt = if (cronStart.isEmpty || cronEnd.isEmpty) None
- else Some(({cronStart0=new CronSpec(cronStart);cronStart0},
- {cronEnd0=new CronSpec(cronEnd);cronEnd0}))
- val ts=Timeslot(start,end)
- if(printScreen) Console.err.println("Timeslot: " + ts)
- val efu = new EffectiveUnitPrice(v,opt)
- val (l1,l2) = efu.splitTimeslot(ts)
- noOverlap(l1,l2)
- singleT(ts,l1,l2)
- if(cronStart0!=null && cronEnd0!=null){
- for {
- t <- l1
- } {
- cronOK(cronStart0,t.from)
- cronOK(cronEnd0,t.to)
- }
- for {
- t <- l2
- } {
- cronNotOK(cronStart0,t.from)
- cronNotOK(cronEnd0,t.to)
- }
- }
- (l1,l2)
- }
-
- private def print(i:Intervals){
- if(printScreen) {
- Console.err.println("BEGIN INTERVAL")
- for { ii <-i } Console.err.println(ii)
- Console.err.println("END INTERVAL")
- }
- }
-
-
- @Test
- def splitTest1 = {
- testEFU(
- 1321621969000L, //Fri Nov 18 15:12:49 +0200 2011
- 1324214719000L, //Sun Dec 18 15:25:19 +0200 2011
- 5.0,
- "33 12 * * *",
- "33 13 * * *"
- )
- val (l2_a,l2_b) = testEFU(
- 1321621969000L, //Fri Nov 18 15:12:49 +0200 2011
- 1321795519000L, //Sun Nov 20 15:25:19 +0200 2011
- 8.0,
- "15 12 * * *",
- "33 13 * * *"
- )
- assert(l2_a.size == 2)
- print(l2_a)
-
- val (l3_a,l3_b) = testEFU(
- 1321621969000L, //Fri Nov 18 15:12:49 +0200 2011
- 1321795519000L, //Sun Nov 20 15:25:19 +0200 2011
- 10.0,
- "33 12 * * *",
- "33 13 * * *"
- )
- print(l3_a)
- assert(l3_a.size == 2)
-
- val (l4_a,l4_b) = testEFU(
- 1321621969000L, //Fri Nov 18 15:12:49 +0200 2011
- 1321795519000L, //Sun Nov 20 15:25:19 +0200 2011
- 15.0,
- "33 12 * * *",
- "33 13 * * *"
- )
- print(l4_a)
- assert(l4_a.size == 2)
-
- ()
- }
-
-}
package gr.grnet.aquarium.user
import gr.grnet.aquarium.store.memory.MemStoreProvider
-import gr.grnet.aquarium.logic.accounting.dsl._
import gr.grnet.aquarium.util.{Loggable, ContextualLogger}
import gr.grnet.aquarium.simulation._
import gr.grnet.aquarium.uid.{UIDGenerator, ConcurrentVMLocalUIDGenerator}
import org.junit.{Assert, Ignore, Test}
import gr.grnet.aquarium.logic.accounting.algorithm.{ExecutableChargingBehaviorAlgorithm, CostPolicyAlgorithmCompiler}
-import gr.grnet.aquarium.{Timespan, Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
+import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder, AquariumException}
import gr.grnet.aquarium.computation.reason.{NoSpecificChangeReason, MonthlyBillingCalculation}
import gr.grnet.aquarium.util.date.MutableDateCalc
import gr.grnet.aquarium.computation.BillingMonthInfo
-import gr.grnet.aquarium.computation.state.{UserStateBootstrap, UserState}
+import gr.grnet.aquarium.computation.state.UserState
import gr.grnet.aquarium.charging._
import gr.grnet.aquarium.policy.{PolicyDefinedFullPriceTableRef, StdUserAgreement, EffectiveUnitPrice, EffectivePriceTable, FullPriceTable, ResourceType, StdPolicy, PolicyModel}
import gr.grnet.aquarium.Timespan
-import scala.Some
import gr.grnet.aquarium.computation.state.UserStateBootstrap
-import gr.grnet.aquarium.simulation.AquariumSim
-import gr.grnet.aquarium.simulation.ClientSim
/**
parentID = None,
validityTimespan = Timespan(0),
resourceTypes = Set(
- ResourceType("bandwidth", "MB/Hr", DiscreteChargingBehavior),
- ResourceType("vmtime", "Hr", OnOffChargingBehavior),
- ResourceType("diskspace", "MB/Hr", ContinuousChargingBehavior)
+ ResourceType("bandwidth", "MB/Hr", classOf[DiscreteChargingBehavior].getName),
+ ResourceType("vmtime", "Hr", classOf[OnOffChargingBehavior].getName),
+ ResourceType("diskspace", "MB/Hr", classOf[ContinuousChargingBehavior].getName)
),
- chargingBehaviorClasses = Set(
- DiscreteChargingBehavior.getClass.getName,
- OnOffChargingBehavior.getClass.getName,
- ContinuousChargingBehavior.getClass.getName
+ chargingBehaviors = Set(
+ classOf[DiscreteChargingBehavior].getName,
+ classOf[OnOffChargingBehavior].getName,
+ classOf[ContinuousChargingBehavior].getName,
+ classOf[OnceChargingBehavior].getName
),
roleMapping = Map(
"default" -> FullPriceTable(Map(
def apply(vars: Map[ChargingInput, Any]): Double = {
vars.apply(ChargingBehaviorNameInput) match {
- case ChargingBehaviorNames.continuous ⇒
+ case ChargingBehaviorAliases.continuous ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val oldTotalAmount = vars(OldTotalAmountInput).asInstanceOf[Double]
val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
creditsForContinuous(timeDelta, oldTotalAmount)
- case ChargingBehaviorNames.discrete ⇒
+ case ChargingBehaviorAliases.discrete ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
creditsForDiscrete(currentValue)
- case ChargingBehaviorNames.onoff ⇒
+ case ChargingBehaviorAliases.onoff ⇒
val unitPrice = vars(UnitPriceInput).asInstanceOf[Double]
val timeDelta = vars(TimeDeltaInput).asInstanceOf[Double]
creditsForOnOff(timeDelta)
- case ChargingBehaviorNames.once ⇒
+ case ChargingBehaviorAliases.once ⇒
val currentValue = vars(CurrentValueInput).asInstanceOf[Double]
currentValue
override def toString = "DefaultAlgorithm(%s)".format(
Map(
- ChargingBehaviorNames.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
- ChargingBehaviorNames.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
- ChargingBehaviorNames.onoff -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
- ChargingBehaviorNames.once -> "currentValue"))
+ ChargingBehaviorAliases.continuous -> "hrs(timeDelta) * oldTotalAmount * %s".format(ContinuousUnitPrice),
+ ChargingBehaviorAliases.discrete -> "currentValue * %s".format(DiscreteUnitPrice),
+ ChargingBehaviorAliases.onoff -> "hrs(timeDelta) * %s".format(OnOffUnitPrice),
+ ChargingBehaviorAliases.once -> "currentValue"))
}
val DefaultCompiler = new CostPolicyAlgorithmCompiler {