2 * Copyright 2011-2012 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.charging
38 import gr.grnet.aquarium.Aquarium
39 import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
40 import gr.grnet.aquarium.charging.wallet.WalletEntry
41 import gr.grnet.aquarium.computation.BillingMonthInfo
42 import gr.grnet.aquarium.event.model.resource.{StdResourceEvent, ResourceEventModel}
43 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
44 import gr.grnet.aquarium.policy.{FullPriceTable, ResourceType}
45 import gr.grnet.aquarium.util.LogHelpers.Debug
46 import scala.collection.mutable
47 import gr.grnet.aquarium.event.model.EventModel
50 * In practice a resource usage will be charged for the total amount of usage
51 * between resource usage changes.
53 * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
55 * @author Christos KK Loverdos <loverdos@gmail.com>
57 final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
59 def computeCreditsToSubtract(
60 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
62 timeDeltaMillis: Long,
64 ): (Double /* credits */, String /* explanation */) = {
66 val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
67 val credits = HrsOfMillis(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
68 val explanation = "Hours(%s) * MBs(%s) * UnitPrice(%s)".format(
69 HrsOfMillis(timeDeltaMillis),
70 MBsOfBytes(oldAccumulatingAmount),
74 (credits, explanation)
77 def computeSelectorPath(
78 workingChargingBehaviorDetails: mutable.Map[String, Any],
79 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
80 currentResourceEvent: ResourceEventModel,
81 referenceTimeslot: Timeslot,
84 List(FullPriceTable.DefaultSelectorKey)
87 def initialChargingDetails: Map[String, Any] = Map()
89 def computeNewAccumulatingAmount(
90 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
91 eventDetails: Map[String, String]
93 workingResourceInstanceChargingState.oldAccumulatingAmount +
94 workingResourceInstanceChargingState.currentValue
97 def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
98 val details = resourceEvent.details
99 val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
101 resourceEvent.withDetails(newDetails, newOccurredMillis)
104 override def processResourceEvent(
106 resourceEvent: ResourceEventModel,
107 resourceType: ResourceType,
108 billingMonthInfo: BillingMonthInfo,
109 workingResourcesChargingState: WorkingResourcesChargingState,
110 userAgreements: AgreementHistoryModel,
111 totalCredits: Double,
112 walletEntryRecorder: WalletEntry ⇒ Unit
115 // 1. Ensure proper initial state per resource and per instance
116 ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
118 // 2. Fill in data from the new event
119 val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
120 val workingResourcesChargingStateDetails = workingResourcesChargingState.details
121 val instanceID = resourceEvent.instanceID
122 val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
123 fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
125 val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match {
126 case Some(previousEvent) ⇒
127 Debug(logger, "I have previous event %s", previousEvent.toDebugString)
132 // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
133 // Let's see if we can create a dummy previous event.
134 Debug(logger, "First event of its kind %s", resourceEvent.toDebugString)
136 val dummyFirstEventDetails = Map(
137 ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
138 ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
139 ResourceEventModel.Names.details_aquarium_reference_event_id -> resourceEvent.id,
140 ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> resourceEvent.stringIDInStoreOrEmpty
143 val dummyFirstEventValue = 0.0 // TODO From configuration
144 val dummyFirstEvent = resourceEvent.withDetailsAndValue(
145 dummyFirstEventDetails,
146 dummyFirstEventValue,
147 billingMonthInfo.monthStartMillis // TODO max(billingMonthInfo.monthStartMillis, userAgreementModel.validFromMillis)
150 Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString)
155 val retval = computeWalletEntriesForNewEvent(
160 Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis),
161 userAgreements.agreementByTimeslot,
162 workingResourcesChargingStateDetails,
163 workingResourceInstanceChargingState,
164 aquarium.policyStore,
168 // We need just one previous event, so we update it
169 workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent)
174 def createVirtualEventsForRealtimeComputation(
176 resourceTypeName: String,
177 resourceInstanceID: String,
178 eventOccurredMillis: Long,
179 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState
180 ): List[ResourceEventModel] = {
182 ChargingBehavior.VirtualEventsIDGen.nextUID(),
190 EventModel.EventVersion_1_0,
192 ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
193 ResourceEventModel.Names.details_aquarium_is_realtime_virtual -> "true"
199 object ContinuousChargingBehavior {
200 private[this] final val TheOne = new ContinuousChargingBehavior
202 def apply(): ContinuousChargingBehavior = TheOne