policies.filter {
a => a._1.from.before(from) &&
a._1.to.after(to)
- }.values.toList
+ }.valuesIterator.toList
}
def policies(t: Timeslot): List[DSLPolicy] = policies(t.from, t.to)
*/
def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
+ def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double
+
+ def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double
+
+ /**
+ * Th every initial amount.
+ */
+ def getResourceInstanceInitialAmount: Double
+
/**
* Get the value that will be used in credit calculation in Accounting.chargeEvents
*/
def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double]
+ def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double
+
/**
* An event's value by itself should carry enough info to characterize it billable or not.
*
def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
oldAmountM match {
- case Just(oldValue) ⇒
- Just(oldValue + newEventValue)
+ case Just(oldAmount) ⇒
+ Just(oldAmount + newEventValue)
case NoVal ⇒
Failed(new Exception("NoVal for oldValue instead of Just"))
case Failed(e, m) ⇒
}
}
+ def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
+ oldAmount + newEventValue
+ }
+
+ def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
+ oldAmount
+ }
+
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
+ }
+
def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
oldAmountM
}
+
+ def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
+ oldAmount
+ }
+
}
/**
def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
Just(newEventValue)
}
+
+ def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
+ newEventValue
+ }
- def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
+ def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
import OnOffCostPolicyValues.{ON, OFF}
+ oldAmount match {
+ case ON ⇒ /* implicit off at the end of the billing period */ OFF
+ case OFF ⇒ OFF
+ }
+ }
- def exception(rs: OnOffPolicyResourceState) =
- new Exception("Resource state transition error (%s -> %s)".format(rs, rs))
- def failed(rs: OnOffPolicyResourceState) =
- Failed(exception(rs))
-
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
+ }
+
+ def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
oldAmountM match {
- case Just(oldValue) ⇒
- (oldValue, newEventValue) match {
- case (ON, ON) ⇒
- failed(OnResourceState)
- case (ON, OFF) ⇒
- Just(OFF)
- case (OFF, ON) ⇒
- Just(ON)
- case (OFF, OFF) ⇒
- failed(OffResourceState)
- }
-
+ case Just(oldAmount) ⇒
+ Maybe(getValueForCreditCalculation(oldAmount, newEventValue))
case NoVal ⇒
Failed(new Exception("NoVal for oldValue instead of Just"))
case Failed(e, m) ⇒
}
}
+ def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
+ import OnOffCostPolicyValues.{ON, OFF}
+
+ def exception(rs: OnOffPolicyResourceState) =
+ new Exception("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 isBillableEventBasedOnValue(newEventValue: Double) = {
OnOffCostPolicyValues.isOFF(newEventValue)
}
override def resourceEventValueIsDiff = true
def computeNewResourceInstanceAmount(oldAmountM: Maybe[Double], newEventValue: Double) = {
- Just(newEventValue)
+ oldAmountM.map(_ + newEventValue)
+ }
+
+ def computeNewResourceInstanceAmount(oldAmount: Double, newEventValue: Double): Double = {
+ oldAmount + newEventValue
+ }
+
+ def computeResourceInstanceAmountForNewBillingPeriod(oldAmount: Double): Double = {
+ 0.0 // ?? def getResourceInstanceInitialAmount
+ }
+
+ def getResourceInstanceInitialAmount: Double = {
+ 0.0
}
def getValueForCreditCalculation(oldAmountM: Maybe[Double], newEventValue: Double): Maybe[Double] = {
Just(newEventValue)
}
+
+ def getValueForCreditCalculation(oldAmount: Double, newEventValue: Double): Double = {
+ newEventValue
+ }
}
/**
ensureNoGaps(data.sortWith((a,b) => if (b.validFrom > a.validFrom) true else false))
def ensureNoGaps(agreements: List[Agreement]): Unit = agreements match {
- case h :: t => assert(h.validTo - t.head.validFrom == 1); ensureNoGaps(t)
+ case ha :: (t @ (hb :: tail)) => assert(ha.validTo - hb.validFrom == 1); ensureNoGaps(t)
case h :: Nil => assert(h.validTo == -1)
+ case Nil => ()
}
/**
* - If the resource is simple, the (name, instanceId) is (DSLResource.name, "1")
*
*/
-case class ResourceInstanceSnapshot(
- name: String,
- instanceId: String,
- data: Double,
- snapshotTime: Long)
+case class ResourceInstanceSnapshot(/**
+ * Same as `resource` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
+ */
+ resource: String,
+
+ /**
+ * Same as `instanceId` of [[gr.grnet.aquarium.logic.events.ResourceEvent]]
+ */
+ instanceId: String,
+ data: Double, // FIXME: data is a missleading name here
+ snapshotTime: Long)
extends UserDataSnapshot[Double] {
/**
*/
def instanceAmount = data
- def isSameResource(name: String, instanceId: String) = {
- this.name == name &&
+ def isSameResourceInstance(resource: String, instanceId: String) = {
+ this.resource == resource &&
this.instanceId == instanceId
}
}
* This representation is convenient for computations and updating, while the
* [[gr.grnet.aquarium.user.OwnedResourcesSnapshot]] representation is convenient for JSON serialization.
*/
-class OwnedResourcesMap(map: Map[(String, String), (Double, Long)]) {
+class OwnedResourcesMap(resourcesMap: Map[(String, String), (Double, Long)]) {
def toResourcesSnapshot(snapshotTime: Long): OwnedResourcesSnapshot =
OwnedResourcesSnapshot(
- map map {
+ resourcesMap map {
case ((name, instanceId), (value, snapshotTime)) ⇒
ResourceInstanceSnapshot(name, instanceId, value, snapshotTime
)} toList,
extends UserDataSnapshot[List[ResourceInstanceSnapshot]] with JsonSupport {
def toResourcesMap: OwnedResourcesMap = {
- val tuples = for(rc <- data) yield ((rc.name, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
+ val tuples = for(rc <- data) yield ((rc.resource, rc.instanceId), (rc.instanceAmount, rc.snapshotTime))
new OwnedResourcesMap(Map(tuples.toSeq: _*))
}
- def findResourceSnapshot(name: String, instanceId: String): Option[ResourceInstanceSnapshot] =
- data.find { x => name == x.name && instanceId == x.instanceId }
+ def resourceInstanceSnapshots = data
- def addOrUpdateResourceSnapshot(name: String, // resource name
- instanceId: String, // resource instance id
- newRCInstanceAmount: Double,
- snapshotTime: Long): (OwnedResourcesSnapshot, Option[ResourceInstanceSnapshot], ResourceInstanceSnapshot) = {
+ def resourceInstanceSnapshotsExcept(resource: String, instanceId: String) = {
+ // Unfortunately, we have to use a List for data, since JSON serialization is not as flexible
+ // (at least out of the box). Thus, the update is O(L), where L is the length of the data List.
+ resourceInstanceSnapshots.filterNot(_.isSameResourceInstance(resource, instanceId))
+ }
+
+ def findResourceInstanceSnapshot(resource: String, instanceId: String): Option[ResourceInstanceSnapshot] = {
+ data.find(x => resource == x.resource && instanceId == x.instanceId)
+ }
+
+ def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
+ findResourceInstanceSnapshot(resource, instanceId).map(_.instanceAmount).getOrElse(defaultValue)
+ }
+
+ def computeResourcesSnapshotUpdate(resource: String, // resource name
+ instanceId: String, // resource instance id
+ newAmount: Double,
+ snapshotTime: Long): (OwnedResourcesSnapshot,
+ Option[ResourceInstanceSnapshot],
+ ResourceInstanceSnapshot) = {
- val newRCInstance = ResourceInstanceSnapshot(name, instanceId, newRCInstanceAmount, snapshotTime)
- val oldRCInstanceOpt = this.findResourceSnapshot(name, instanceId)
+ val newResourceInstance = ResourceInstanceSnapshot(resource, instanceId, newAmount, snapshotTime)
+ val oldResourceInstanceOpt = this.findResourceInstanceSnapshot(resource, instanceId)
- val newData = oldRCInstanceOpt match {
- case Some(oldRCInstance) ⇒
+ val newResourceInstances = oldResourceInstanceOpt match {
+ case Some(oldResourceInstance) ⇒
// Resource instance found, so delete the old one and add the new one
- newRCInstance :: (data.filterNot(_.isSameResource(name, instanceId)))
+ newResourceInstance :: resourceInstanceSnapshotsExcept(resource, instanceId)
case None ⇒
// Resource not found, so this is the first time and we just add the new snapshot
- newRCInstance :: data
+ newResourceInstance :: resourceInstanceSnapshots
}
- val newOwnedResources = this.copy(data = newData, snapshotTime = snapshotTime)
+ val newOwnedResources = OwnedResourcesSnapshot(newResourceInstances, snapshotTime)
- (newOwnedResources, oldRCInstanceOpt, newRCInstance)
+ (newOwnedResources, oldResourceInstanceOpt, newResourceInstance)
}
}
}
}
+ def findResourceInstanceSnapshot(resource: String, instanceId: String): Maybe[ResourceInstanceSnapshot] = {
+ ownedResources.findResourceInstanceSnapshot(resource, instanceId)
+ }
+
+ def getResourceInstanceAmount(resource: String, instanceId: String, defaultValue: Double): Double = {
+ ownedResources.getResourceInstanceAmount(resource, instanceId, defaultValue)
+ }
+
+ def copyForResourcesSnapshotUpdate(resource: String, // resource name
+ instanceId: String, // resource instance id
+ newAmount: Double,
+ snapshotTime: Long): UserState = {
+
+ val (newResources, _, _) = ownedResources.computeResourcesSnapshotUpdate(resource, instanceId, newAmount, snapshotTime)
+
+ this.copy(
+ ownedResources = newResources,
+ stateChangeCounter = this.stateChangeCounter + 1)
+ }
+
def resourcesMap = ownedResources.toResourcesMap
def safeCredits = credits match {
- case c @ CreditSnapshot(date, millis) ⇒ c
- case _ ⇒ CreditSnapshot(0, 0)
+ case c @ CreditSnapshot(_, _) ⇒ c
+ case _ ⇒ CreditSnapshot(0.0, 0)
}
}
)
}
+ def createFirstUserState(userId: String, agreementName: String, policy: DSLPolicy) = {
+ val resources = policy.resources
+
+ val now = 0L
+ UserState(
+ userId,
+ now,
+ 0L,
+ false,
+ null,
+ 0L,
+ ActiveSuspendedSnapshot(false, now),
+ CreditSnapshot(0, now),
+ AgreementSnapshot(Agreement(agreementName, now, now) :: Nil, now),
+ RolesSnapshot(List(), now),
+ PaymentOrdersSnapshot(Nil, now),
+ OwnedGroupsSnapshot(Nil, now),
+ GroupMembershipsSnapshot(Nil, now),
+ OwnedResourcesSnapshot(List(), now)
+ )
+ }
+
/**
* Get the user state as computed up to (and not including) the start of the new billing period.
*
* Find the previous resource event, if needed by the event's cost policy,
* in order to use it for any credit calculations.
*/
- def findPreviousRCEventOf(previousRCEventsMap: mutable.Map[ResourceEvent.FullResourceType, ResourceEvent],
- rcEvent: ResourceEvent,
- costPolicy: DSLCostPolicy): Maybe[ResourceEvent] = {
+ def findPreviousRCEventOf(rcEvent: ResourceEvent,
+ costPolicy: DSLCostPolicy,
+ previousRCEventsMap: mutable.Map[ResourceEvent.FullResourceType, ResourceEvent]): Maybe[ResourceEvent] = {
if(costPolicy.needsPreviousEventForCreditCalculation) {
// Get a previous resource only if this is needed by the policy
// OK. Now that we have a user state to start with (= start of billing period reference point),
// let us deal with the events themselves.
val billingStartMillis = billingMonthStartDate.toMillis
- val billingStopMillis = billingMonthStartDate.endOfThisMonth.toMillis
+ val billingStopMillis = billingMonthStartDate.endOfThisMonth.toMillis
val allBillingPeriodRelevantRCEvents = rcEventStore.findAllRelevantResourceEventsForBillingPeriod(userId, billingStartMillis, billingStopMillis)
type FullResourceType = ResourceEvent.FullResourceType
val previousRCEventsMap = mutable.Map[FullResourceType, ResourceEvent]()
val impliedRCEventsMap = mutable.Map[FullResourceType, ResourceEvent]() // those which do not exists but are
// implied in order to do billing calculations (e.g. the "off" vmtime resource event)
- var workingUserState = newStartUserState
+
+ // Our temporary state holder.
+ var _workingUserState = newStartUserState
+ val nowMillis = TimeHelpers.nowMillis
for(newRCEvent <- allBillingPeriodRelevantRCEvents) {
+ val resource = newRCEvent.resource
+ val instanceId = newRCEvent.instanceId
+
// We need to do these kinds of calculations:
// 1. Credit state calculations
// 2. Resource state calculations
//
// BUT ALL THE ABOVE SHOULD NOT BE CONSIDERED HERE; RATHER THEY ARE POLYMORPHIC BEHAVIOURS
- // We need:
- // A. The previous event
-
+ // What we need to do is:
+ // A. Update user state with new resource instance amount
+ // B. Update user state with new credit
+ // C. Update ??? state with wallet entries
// The DSLCostPolicy for the resource does not change, so it is safe to use the default DSLPolicy to obtain it.
val costPolicyM = newRCEvent.findCostPolicy(defaultPolicy)
costPolicyM match {
case Just(costPolicy) ⇒
- val previousRCEventM = findPreviousRCEventOf(previousRCEventsMap, newRCEvent, costPolicy)
- val previousRCEventValueM = previousRCEventM.map(_.value)
-// val previousRCInstanceAmount = workingUserState.ownedResources.
-
- // 1. Update resource state
- val newRCInstanceAmountM = costPolicy.computeNewResourceInstanceAmount(previousRCEventValueM, newRCEvent.value)
- newRCInstanceAmountM match {
- case Just(newRCInstanceAmount) ⇒
- workingUserState.ownedResources.addOrUpdateResourceSnapshot(
- newRCEvent.resource,
- newRCEvent.instanceId,
- newRCInstanceAmount,
- TimeHelpers.nowMillis)
- case NoVal ⇒
- () // ERROR
- case failed @ Failed(_, _) ⇒
- () // ERROR
- }
-
- // 2. Update credit state
-
- // 3. Calc wallet entries
+ ///////////////////////////////////////
+ // A. Update user state with new resource instance amount
+ // TODO: Check if we are at beginning of billing period, so as to use
+ // costPolicy.computeResourceInstanceAmountForNewBillingPeriod
+ val DefaultResourceInstanceAmount = costPolicy.getResourceInstanceInitialAmount
+
+ val previousAmount = currentUserState.getResourceInstanceAmount(resource, instanceId, DefaultResourceInstanceAmount)
+ val newAmount = costPolicy.computeNewResourceInstanceAmount(previousAmount, newRCEvent.value)
+
+ _workingUserState = _workingUserState.copyForResourcesSnapshotUpdate(resource, instanceId, newAmount, nowMillis)
+ // A. Update user state with new resource instance amount
+ ///////////////////////////////////////
+
+
+ ///////////////////////////////////////
+ // B. Update user state with new credit
+ val previousRCEventM = findPreviousRCEventOf(newRCEvent, costPolicy, previousRCEventsMap)
+ _workingUserState.findResourceInstanceSnapshot(resource, instanceId)
+ // B. Update user state with new credit
+ ///////////////////////////////////////
+
+
+ ///////////////////////////////////////
+ // C. Update ??? state with wallet entries
+
+ // C. Update ??? state with wallet entries
+ ///////////////////////////////////////
case NoVal ⇒
() // ERROR