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.{AquariumInternalError, 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
51 * In practice a resource usage will be charged for the total amount of usage
52 * between resource usage changes.
54 * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
56 * @author Christos KK Loverdos <loverdos@gmail.com>
58 final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
60 def computeCreditsToSubtract(
61 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
63 timeDeltaMillis: Long,
65 ): (Double /* credits */, String /* explanation */) = {
67 val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
68 val credits = HrsOfMillis(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
69 val explanation = "Hours(%s) * MBs(%s) * UnitPrice(%s)".format(
70 HrsOfMillis(timeDeltaMillis),
71 MBsOfBytes(oldAccumulatingAmount),
75 (credits, explanation)
78 def computeSelectorPath(
79 workingChargingBehaviorDetails: mutable.Map[String, Any],
80 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
81 currentResourceEvent: ResourceEventModel,
82 referenceTimeslot: Timeslot,
85 List(FullPriceTable.DefaultSelectorKey)
88 def initialChargingDetails: Map[String, Any] = Map()
90 def computeNewAccumulatingAmount(
91 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
92 eventDetails: Map[String, String]
94 workingResourceInstanceChargingState.oldAccumulatingAmount +
95 workingResourceInstanceChargingState.currentValue
98 def constructImplicitEndEventFor(resourceEvent: ResourceEventModel, newOccurredMillis: Long) = {
99 val details = resourceEvent.details
100 val newDetails = ResourceEventModel.setAquariumSyntheticAndImplicitEnd(details)
102 resourceEvent.withDetails(newDetails, newOccurredMillis)
105 override def processResourceEvent(
107 resourceEvent: ResourceEventModel,
108 resourceType: ResourceType,
109 billingMonthInfo: BillingMonthInfo,
110 workingResourcesChargingState: WorkingResourcesChargingState,
111 userAgreements: AgreementHistoryModel,
112 totalCredits: Double,
113 walletEntryRecorder: WalletEntry ⇒ Unit
116 // 1. Ensure proper initial state per resource and per instance
117 ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
119 // 2. Fill in data from the new event
120 val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
121 val workingResourcesChargingStateDetails = workingResourcesChargingState.details
122 val instanceID = resourceEvent.instanceID
123 val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
124 fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
126 val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match {
127 case Some(previousEvent) ⇒
128 Debug(logger, "I have previous event %s", previousEvent.toDebugString)
133 // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
134 // Let's see if we can create a dummy previous event.
135 Debug(logger, "First event of its kind %s", resourceEvent.toDebugString)
137 val dummyFirstEventDetails = Map(
138 ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
139 ResourceEventModel.Names.details_aquarium_is_dummy_first -> "true",
140 ResourceEventModel.Names.details_aquarium_reference_event_id -> resourceEvent.id,
141 ResourceEventModel.Names.details_aquarium_reference_event_id_in_store -> resourceEvent.stringIDInStoreOrEmpty
144 val dummyFirstEventValue = 0.0 // TODO From configuration
146 val millis = userAgreements.agreementByTimeslot.headOption match {
148 throw new AquariumInternalError("No agreement!!!")
149 case Some((_,aggr)) =>
150 val millisAgg = aggr.timeslot.from.getTime
151 val millisMon = billingMonthInfo.monthStartMillis
152 if(millisAgg>millisMon) millisAgg else millisMon
155 val dummyFirstEvent = resourceEvent.withDetailsAndValue(
156 dummyFirstEventDetails,
157 dummyFirstEventValue,
159 Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString)
163 val retval = computeWalletEntriesForNewEvent(
168 Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis),
169 userAgreements.agreementByTimeslot,
170 workingResourcesChargingStateDetails,
171 workingResourceInstanceChargingState,
172 aquarium.policyStore,
176 // We need just one previous event, so we update it
177 workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent)
182 def createVirtualEventsForRealtimeComputation(
184 resourceTypeName: String,
185 resourceInstanceID: String,
186 eventOccurredMillis: Long,
187 workingResourceInstanceChargingState: WorkingResourceInstanceChargingState
188 ): List[ResourceEventModel] = {
190 ChargingBehavior.VirtualEventsIDGen.nextUID(),
198 EventModel.EventVersion_1_0,
200 ResourceEventModel.Names.details_aquarium_is_synthetic -> "true",
201 ResourceEventModel.Names.details_aquarium_is_realtime_virtual -> "true"
207 object ContinuousChargingBehavior {
208 private[this] final val TheOne = new ContinuousChargingBehavior
210 def apply(): ContinuousChargingBehavior = TheOne