Revision 10c87819

b/src/main/scala/gr/grnet/aquarium/Aquarium.scala
290 290

  
291 291
  def initialUserBalance(role: String, referenceTimeMillis: Long): Double = {
292 292
    // FIXME: Where is the mapping?
293
    1000.0
293
    0.0
294 294
  }
295 295

  
296 296
  def chargingBehaviorOf(resourceType: ResourceType): ChargingBehavior = {
b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala
312 312
  /* Convert astakos message for adding credits
313 313
    to a regular RESOURCE message */
314 314
  def onHandleAddCreditsEvent(imEvent : IMEventModel) = {
315
    DEBUG("Got %s", imEvent.toJsonString)
316

  
315 317
    val credits = imEvent.details(IMEventModel.DetailsNames.credits).toInt.toDouble
316 318
    val event = new StdResourceEvent(
317 319
      imEvent.id,
......
325 327
      imEvent.eventVersion,
326 328
      imEvent.details
327 329
    )
328
    //Console.err.println("Event: " + event)
329
    //Console.err.println("Total credits before: " + _workingUserState.totalCredits)
330
    DEBUG("Transformed to %s", event)
331
    DEBUG("Total credits before: %s", _workingUserState.totalCredits)
332
    aquarium.resourceEventStore.insertResourceEvent(event)
330 333
    onProcessResourceEvent(new ProcessResourceEvent(event))
331
    //Console.err.println("Total credits after: " + _workingUserState.totalCredits)
334
    DEBUG("Total credits after: %s", _workingUserState.totalCredits)
332 335
    //Console.err.println("OK.")
333 336
  }
334 337

  
b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehavior.scala
79 79

  
80 80
  /**
81 81
   *
82
   * @return The number of wallet entries recorded and the new total credits
82
   * @return The number of wallet entries recorded and the credit difference generated during processing (these are
83
   *         the credits to subtract from the total credits).
83 84
   */
84 85
  def processResourceEvent(
85 86
      aquarium: Aquarium,
b/src/main/scala/gr/grnet/aquarium/charging/ChargingBehaviorSkeleton.scala
35 35

  
36 36
package gr.grnet.aquarium.charging
37 37

  
38
import scala.collection.immutable
39
import scala.collection.mutable
40

  
41
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
42
import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
43
import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
44
import gr.grnet.aquarium.util._
45
import gr.grnet.aquarium.util.date.TimeHelpers
38
import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
46 39
import gr.grnet.aquarium.charging.wallet.WalletEntry
47 40
import gr.grnet.aquarium.computation.{TimeslotComputations, BillingMonthInfo}
48
import gr.grnet.aquarium.charging.state.{WorkingResourceInstanceChargingState, WorkingResourcesChargingState, AgreementHistoryModel}
41
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
49 42
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
43
import gr.grnet.aquarium.policy.{FullPriceTable, EffectivePriceTable, UserAgreementModel, ResourceType}
50 44
import gr.grnet.aquarium.store.PolicyStore
45
import gr.grnet.aquarium.util._
46
import gr.grnet.aquarium.util.date.TimeHelpers
47
import gr.grnet.aquarium.{Aquarium, AquariumInternalError}
48
import scala.collection.immutable
49
import scala.collection.mutable
50

  
51 51

  
52 52
/**
53 53
 * A charging behavior indicates how charging for a resource will be done
......
82 82
    )
83 83
  }
84 84

  
85
  final protected def ensureWorkingState(
85
  final protected def ensureInitializedWorkingState(
86 86
      workingResourcesChargingState: WorkingResourcesChargingState,
87 87
      resourceEvent: ResourceEventModel
88 88
  ) {
89
    ensureResourcesChargingStateDetails(workingResourcesChargingState.details)
90
    ensureResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
89
    ensureInitializedResourcesChargingStateDetails(workingResourcesChargingState.details)
90
    ensureInitializedResourceInstanceChargingState(workingResourcesChargingState, resourceEvent)
91 91
  }
92 92

  
93
  protected def ensureResourcesChargingStateDetails(
94
    details: mutable.Map[String, Any]
95
  ) {}
93
  protected def ensureInitializedResourcesChargingStateDetails(details: mutable.Map[String, Any]) {}
96 94

  
97
  protected def ensureResourceInstanceChargingState(
95
  protected def ensureInitializedResourceInstanceChargingState(
98 96
      workingResourcesChargingState: WorkingResourcesChargingState,
99 97
      resourceEvent: ResourceEventModel
100 98
  ) {
......
110 108
    }
111 109
  }
112 110

  
111
  protected def fillWorkingResourceInstanceChargingStateFromEvent(
112
      workingResourceInstanceChargingState: WorkingResourceInstanceChargingState,
113
      resourceEvent: ResourceEventModel
114
  ) {
115

  
116
    workingResourceInstanceChargingState.currentValue = resourceEvent.value
117
  }
118

  
113 119
  protected def computeWalletEntriesForNewEvent(
114 120
      resourceEvent: ResourceEventModel,
115 121
      resourceType: ResourceType,
......
196 202

  
197 203
    walletEntryRecorder.apply(newWalletEntry)
198 204

  
199
    (1, newTotalCredits)
205
    (1, sumOfCreditsToSubtract)
200 206
  }
201 207

  
202 208

  
......
217 223
      totalCredits
218 224
    )
219 225

  
220
    fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource)
226
    fullPriceTable.effectivePriceTableOfSelectorForResource(selectorPath, currentResourceEvent.safeResource, logger)
221 227
  }
222 228

  
223 229
  /**
b/src/main/scala/gr/grnet/aquarium/charging/ChargingService.scala
206 206
    }
207 207

  
208 208
    val m0 = TimeHelpers.nowMillis()
209
    val (walletEntriesCount, newTotalCredits) = chargingBehavior.processResourceEvent(
209
    val (walletEntriesCount, creditsToSubtract) = chargingBehavior.processResourceEvent(
210 210
      aquarium,
211 211
      resourceEvent,
212 212
      resourceType,
......
223 223
    }
224 224

  
225 225
    workingUserState.updateLatestResourceEventOccurredMillis(resourceEvent.occurredMillis)
226
    workingUserState.totalCredits = newTotalCredits
226
    workingUserState.totalCredits -= creditsToSubtract
227 227

  
228 228
    true
229 229
  }
b/src/main/scala/gr/grnet/aquarium/charging/ContinuousChargingBehavior.scala
35 35

  
36 36
package gr.grnet.aquarium.charging
37 37

  
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
38 42
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
39 43
import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
40
import gr.grnet.aquarium.policy.ResourceType
41
import gr.grnet.aquarium.charging.state.{AgreementHistoryModel, WorkingResourcesChargingState, WorkingResourceInstanceChargingState}
44
import gr.grnet.aquarium.policy.{FullPriceTable, ResourceType}
45
import gr.grnet.aquarium.util.LogHelpers.Debug
42 46
import scala.collection.mutable
43
import gr.grnet.aquarium.Aquarium
44
import gr.grnet.aquarium.computation.BillingMonthInfo
45
import gr.grnet.aquarium.charging.wallet.WalletEntry
46 47

  
47 48
/**
48 49
 * In practice a resource usage will be charged for the total amount of usage
......
63 64

  
64 65
    val oldAccumulatingAmount = workingResourceInstanceChargingState.oldAccumulatingAmount
65 66
    val credits = hrs(timeDeltaMillis) * oldAccumulatingAmount * unitPrice
66
    val explanation = "Time(%s) * OldTotal(%s) * Unit(%s)".format(
67
    val explanation = "Time(%s) * OldTotal(%s) * UnitPrice(%s)".format(
67 68
      hrs(timeDeltaMillis),
68 69
      oldAccumulatingAmount,
69 70
      unitPrice
......
80 81
      referenceTimeslot: Timeslot,
81 82
      totalCredits: Double
82 83
  ): List[String] = {
83
    Nil
84
    List(FullPriceTable.DefaultSelectorKey)
84 85
  }
85 86

  
86 87
  def initialChargingDetails: Map[String, Any] = Map()
......
102 103

  
103 104
  override def processResourceEvent(
104 105
       aquarium: Aquarium,
105
       currentResourceEvent: ResourceEventModel,
106
       resourceEvent: ResourceEventModel,
106 107
       resourceType: ResourceType,
107 108
       billingMonthInfo: BillingMonthInfo,
108 109
       workingResourcesChargingState: WorkingResourcesChargingState,
......
110 111
       totalCredits: Double,
111 112
       walletEntryRecorder: WalletEntry ⇒ Unit
112 113
   ): (Int, Double) = {
113
    (0,0)
114

  
115
    // 1. Ensure proper initial state per resource and per instance
116
    ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
117

  
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)
124

  
125
    val previousEvent = workingResourceInstanceChargingState.previousEvents.headOption match {
126
      case Some(previousEvent) ⇒
127
        Debug(logger, "I have previous event %s", previousEvent.toDebugString)
128
        previousEvent
129

  
130

  
131
      case None ⇒
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)
135

  
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
141
        )
142

  
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)
148
        )
149

  
150
        Debug(logger, "Dummy first event %s", dummyFirstEvent.toDebugString)
151

  
152
        dummyFirstEvent
153
    }
154

  
155
    val retval = computeWalletEntriesForNewEvent(
156
      resourceEvent,
157
      resourceType,
158
      billingMonthInfo,
159
      totalCredits,
160
      Timeslot(previousEvent.occurredMillis, resourceEvent.occurredMillis),
161
      userAgreements.agreementByTimeslot,
162
      workingResourcesChargingStateDetails,
163
      workingResourceInstanceChargingState,
164
      aquarium.policyStore,
165
      walletEntryRecorder
166
    )
167

  
168
    // We need just one previous event, so we update it
169
    workingResourceInstanceChargingState.setOnePreviousEvent(resourceEvent)
170

  
171
    retval
114 172
  }
115 173
}
116 174

  
b/src/main/scala/gr/grnet/aquarium/charging/OnceChargingBehavior.scala
60 60
  ): (Double /* credits */, String /* explanation */) = {
61 61

  
62 62
    val currentValue = workingResourceInstanceChargingState.currentValue
63
    val credits = currentValue
64
    val explanation = "Value(%s)".format(currentValue)
63
    // Always remember to multiply with the `unitPrice`, since it scales the credits, depending on
64
    // the particular resource type tha applies.
65
    val credits = currentValue * unitPrice
66
    val explanation = "Value(%s) * UnitPrice(%s)".format(currentValue, unitPrice)
65 67

  
66 68
    (credits, explanation)
67 69
  }
......
90 92
    // But we cannot just apply them, since we also need to take into account the unit price.
91 93
    // Normally, the unit price is 1.0 but we have the flexibility to allow more stuff).
92 94

  
93
    val instanceID = resourceEvent.instanceID
94
    val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
95

  
96
    // 0. Ensure proper state per resource and per instance
97
    ensureWorkingState(workingResourcesChargingState, resourceEvent)
98

  
99
    // 1. Find the unit price at the moment of the event
95
    // 1. Ensure proper initial state per resource and per instance
96
    ensureInitializedWorkingState(workingResourcesChargingState, resourceEvent)
100 97

  
98
    // 2. Fill in data from the new event
99
    val stateOfResourceInstance = workingResourcesChargingState.stateOfResourceInstance
101 100
    val workingResourcesChargingStateDetails = workingResourcesChargingState.details
101
    val instanceID = resourceEvent.instanceID
102 102
    val workingResourceInstanceChargingState = stateOfResourceInstance(instanceID)
103
    fillWorkingResourceInstanceChargingStateFromEvent(workingResourceInstanceChargingState, resourceEvent)
103 104

  
104 105
    computeWalletEntriesForNewEvent(
105 106
      resourceEvent,
b/src/main/scala/gr/grnet/aquarium/charging/VMChargingBehavior.scala
60 60
  ): (Double /* credits */, String /* explanation */) = {
61 61

  
62 62
    val credits = hrs(timeDeltaMillis) * unitPrice
63
    val explanation = "Time(%s) * Unit(%s)".format(hrs(timeDeltaMillis), unitPrice)
63
    val explanation = "Time(%s) * UnitPrice(%s)".format(hrs(timeDeltaMillis), unitPrice)
64 64

  
65 65
    (credits, explanation)
66 66

  
b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourceInstanceChargingState.scala
79 79
    this.previousValue = this.currentValue
80 80
    this.currentValue = value
81 81
  }
82

  
83
  def setOnePreviousEvent(event: ResourceEventModel) {
84
    this.previousEvents = event :: Nil
85
  }
82 86
}
b/src/main/scala/gr/grnet/aquarium/charging/state/WorkingResourcesChargingState.scala
36 36
package gr.grnet.aquarium.charging.state
37 37

  
38 38
import scala.collection.mutable
39
import gr.grnet.aquarium.event.model.resource.ResourceEventModel
39 40

  
40 41
/**
41
 * Working (mutable state) for a resource instances of the same resource type.
42
 * Working (mutable state) for resource instances of the same resource type.
42 43
 *
43 44
 * @param details Generic state related to the type of resource as a whole
44 45
 * @param stateOfResourceInstance A map from `instanceID` to
......
47 48
 * @author Christos KK Loverdos <loverdos@gmail.com>
48 49
 */
49 50
final class WorkingResourcesChargingState(
50
    val details: mutable.Map[String, Any],
51
    val stateOfResourceInstance: mutable.Map[String, WorkingResourceInstanceChargingState]
51
    val details: mutable.Map[String /* any string */, Any],
52
    val stateOfResourceInstance: mutable.Map[String /* InstanceID */, WorkingResourceInstanceChargingState]
52 53
) {
53 54

  
54 55
  def immutableDetails = Map(this.details.toSeq: _*)
......
64 65
      stateOfResourceInstance = immutableStateOfResourceInstance
65 66
    )
66 67
  }
68

  
69
  /**
70
   * Find the most recent (latest) holder of a resource event.
71
   */
72
//  def findResourceInstanceOfLatestEvent: Option[WorkingResourceInstanceChargingState] = {
73
//    stateOfResourceInstance.values.toArray.sortWith { (a, b) ⇒
74
//      (a.previousEvents, b.previousEvents
75
//    }
76
//  }
67 77
}
b/src/main/scala/gr/grnet/aquarium/policy/FullPriceTable.scala
36 36
package gr.grnet.aquarium.policy
37 37

  
38 38
import gr.grnet.aquarium.AquariumInternalError
39
import scala.annotation.tailrec
40 39
import gr.grnet.aquarium.util.shortNameOfType
40
import gr.grnet.aquarium.util.LogHelpers.Debug
41
import org.slf4j.Logger
42
import scala.annotation.tailrec
41 43

  
42 44
/**
43 45
 * A full price table provides detailed pricing information for all resource types.
......
57 59

  
58 60
  def effectivePriceTableOfSelectorForResource(
59 61
      selectorPath: List[String],
60
      resource: String
62
      resource: String,
63
      logger: Logger
61 64
  ): EffectivePriceTable = {
62 65

  
63 66
    // Most of the code is for exceptional cases, which we identify in detail.
......
67 70
        partialSelectorData: Any
68 71
    ): EffectivePriceTable = {
69 72

  
73
      Debug(logger, "find: ")
74
      Debug(logger, "  partialSelectorPath = %s", partialSelectorPath.mkString("/"))
75
      Debug(logger, "  partialSelectorData = %s", partialSelectorData)
76

  
70 77
      partialSelectorPath match {
71 78
        case selector :: Nil ⇒
72 79
          // One selector, meaning that the selectorData must be a Map[String, EffectivePriceTable]
......
161 168
          }
162 169

  
163 170
        case Nil ⇒
164
          throw new AquariumInternalError("")
171
          throw new AquariumInternalError(
172
            "[AQU-SEL-007] No selector path for resource %s".format(resource)
173
          )
165 174

  
166 175
      }
167 176
    }
168 177

  
178
    Debug(logger, "effectivePriceTableOfSelectorForResource:")
179
    Debug(logger, "  selectorPath = %s", selectorPath.mkString("/"))
180

  
169 181
    val selectorDataOpt = perResource.get(resource)
170 182
    if(selectorDataOpt.isEmpty) {
171 183
      throw new AquariumInternalError("Unknown resource type '%s'", resource)
172 184
    }
173 185
    val selectorData = selectorDataOpt.get
174 186

  
187
    Debug(logger, "  selectorData = %s", selectorData)
175 188
    find(selectorPath, selectorData)
176 189
  }
177 190
}

Also available in: Unified diff