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.charging.state.UserStateModel
39 import gr.grnet.aquarium.computation.BillingMonthInfo
40 import gr.grnet.aquarium.event.{CreditsModel, DetailsModel}
41 import gr.grnet.aquarium.message.MessageConstants
42 import gr.grnet.aquarium.message.avro.gen.{AnyValueMsg, WalletEntryMsg, ResourcesChargingStateMsg, ResourceTypeMsg, ResourceInstanceChargingStateMsg, ResourceEventMsg}
43 import gr.grnet.aquarium.message.avro.{MessageHelpers, AvroHelpers, MessageFactory}
44 import gr.grnet.aquarium.util.LogHelpers.Debug
45 import gr.grnet.aquarium.{AquariumInternalError, Aquarium}
48 * In practice a resource usage will be charged for the total amount of usage
49 * between resource usage changes.
51 * Example resource that might be adept to a continuous policy is diskspace, as in Pithos+ service.
53 * @author Christos KK Loverdos <loverdos@gmail.com>
55 final class ContinuousChargingBehavior extends ChargingBehaviorSkeleton(Nil) {
57 def computeCreditsToSubtract(
58 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
60 timeDeltaMillis: Long,
62 ): (Double /* credits */, String /* explanation */) = {
64 val oldAccumulatingAmount = resourceInstanceChargingState.getOldAccumulatingAmount
65 val credits = HrsOfMillis(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
66 val explanation = "Hours(%s) * MBs(%s) * UnitPrice(%s)".format(
67 HrsOfMillis(timeDeltaMillis),
68 MBsOfBytes(oldAccumulatingAmount),
72 (credits, explanation)
75 def computeSelectorPath(
76 chargingBehaviorDetails: DetailsModel.Type,
77 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
78 currentResourceEvent: ResourceEventMsg,
79 referenceStartMillis: Long,
80 referenceStopMillis: Long,
83 List(MessageConstants.DefaultSelectorKey)
86 def initialChargingDetails = {
90 def computeNewAccumulatingAmount(
91 resourceInstanceChargingState: ResourceInstanceChargingStateMsg,
92 eventDetails: DetailsModel.Type
95 val oldAccumulatingAmount = CreditsModel.from(resourceInstanceChargingState.getOldAccumulatingAmount)
96 val currentValue = CreditsModel.from(resourceInstanceChargingState.getCurrentValue)
98 CreditsModel.add(oldAccumulatingAmount, currentValue)
101 def constructImplicitEndEventFor(resourceEvent: ResourceEventMsg, newOccurredMillis: Long) = {
102 val details = resourceEvent.getDetails
103 val newDetails = DetailsModel.copyOf(details)
104 MessageHelpers.setAquariumSyntheticAndImplicitEnd(newDetails)
106 // FIXME: What value ?
107 ResourceEventMsg.newBuilder(resourceEvent).
108 setDetails(newDetails).
109 setOccurredMillis(newOccurredMillis).
110 setReceivedMillis(newOccurredMillis).
114 def processResourceEvent(
116 resourceEvent: ResourceEventMsg,
117 resourceType: ResourceTypeMsg,
118 billingMonthInfo: BillingMonthInfo,
119 resourcesChargingState: ResourcesChargingStateMsg,
120 userStateModel: UserStateModel,
121 walletEntryRecorder: WalletEntryMsg ⇒ Unit
122 ): (Int, CreditsModel.Type) = {
124 // 1. Ensure proper initial state per resource and per instance
125 ensureInitializedWorkingState(resourcesChargingState, resourceEvent)
127 // 2. Fill in data from the new event
128 val stateOfResourceInstance = resourcesChargingState.getStateOfResourceInstance
129 val resourcesChargingStateDetails = resourcesChargingState.getDetails
130 val instanceID = resourceEvent.getInstanceID
131 val resourceInstanceChargingState = stateOfResourceInstance.get(instanceID)
132 fillWorkingResourceInstanceChargingStateFromEvent(resourceInstanceChargingState, resourceEvent)
134 val userAgreementHistoryModel = userStateModel.userAgreementHistoryModel
135 val previousEvents = resourceInstanceChargingState.getPreviousEvents
136 val previousEvent = previousEvents.size() match {
138 // We do not have the needed previous event, so this must be the first resource event of its kind, ever.
139 // Let's see if we can create a dummy previous event.
140 Debug(logger, "First event of its kind %s", AvroHelpers.jsonStringOfSpecificRecord(resourceEvent))
142 val dummyFirstEventValue = "0.0" // TODO ? From configuration
144 val millis = userAgreementHistoryModel.agreementByTimeslot.headOption match {
146 throw new AquariumInternalError("No agreement!!!") // FIXME Better explanation
147 case Some((_,aggr)) =>
148 val millisAgg = aggr.timeslot.from.getTime
149 val millisMon = billingMonthInfo.monthStartMillis
150 if(millisAgg>millisMon) millisAgg else millisMon
153 val dummyFirstEvent = constructDummyFirstEventFor(resourceEvent, millis, dummyFirstEventValue)
155 Debug(logger, "Dummy first event %s", AvroHelpers.jsonStringOfSpecificRecord(dummyFirstEvent))
160 val previousEvent = previousEvents.get(0) // head is most recent
161 Debug(logger, "I have previous event %s", AvroHelpers.jsonStringOfSpecificRecord(previousEvent))
166 val retval = computeWalletEntriesForNewEvent(
170 userStateModel.totalCredits,
171 previousEvent.getOccurredMillis,
172 resourceEvent.getOccurredMillis,
173 userAgreementHistoryModel.agreementByTimeslot,
174 resourcesChargingStateDetails,
175 resourceInstanceChargingState,
180 // We need just one previous event, so we update it
181 MessageHelpers.setOnePreviousEvent(resourceInstanceChargingState, resourceEvent)
186 def createVirtualEventsForRealtimeComputation(
188 resourceTypeName: String,
189 resourceInstanceID: String,
190 eventOccurredMillis: Long,
191 resourceInstanceChargingState: ResourceInstanceChargingStateMsg
192 ): List[ResourceEventMsg] = {
193 // FIXME This is too adhoc...
194 val path = resourceInstanceChargingState.getPreviousEvents.size() match {
196 "unknown" // FIXME This should not happen. Throw?
199 val previousEvent = resourceInstanceChargingState.getPreviousEvents.get(0)
200 previousEvent.getDetails.get(MessageConstants.DetailsKeys.path) match {
202 "unknown" // FIXME This should not happen. Throw?
205 MessageHelpers.stringOfAnyValueMsg(path)
209 // FIXME This is too adhoc...
210 val action = resourceInstanceChargingState.getPreviousEvents.size() match {
212 "unknown" // FIXME This should not happen. Throw?
215 val previousEvent = resourceInstanceChargingState.getPreviousEvents.get(0)
216 previousEvent.getDetails.get(MessageConstants.DetailsKeys.action) match {
221 MessageHelpers.stringOfAnyValueMsg(action)
225 MessageFactory.newResourceEventMsg(
226 MessageHelpers.VirtualEventsIDGen.nextUID(),
234 MessageConstants.EventVersion_1_0,
235 MessageFactory.newDetails(
236 MessageFactory.newBooleanDetail(MessageConstants.DetailsKeys.aquarium_is_synthetic, true),
237 MessageFactory.newBooleanDetail(MessageConstants.DetailsKeys.aquarium_is_realtime_virtual, true),
238 MessageFactory.newStringDetail(MessageConstants.DetailsKeys.path, path),
239 MessageFactory.newStringDetail(MessageConstants.DetailsKeys.action, action)
245 object ContinuousChargingBehavior {
246 private[this] final val TheOne = new ContinuousChargingBehavior
248 def apply(): ContinuousChargingBehavior = TheOne