Calculate user state from resource events (wip).
[aquarium] / src / main / scala / gr / grnet / aquarium / logic / events / ResourceType.scala
1 /*
2  * Copyright 2011 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
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.
16  *
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.
29  *
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.
34  */
35
36 package gr.grnet.aquarium.logic.events
37
38 import gr.grnet.aquarium.util.TimeHelpers.nowMillis
39 import gr.grnet.aquarium.user._
40
41
42 /**
43  * This is an object representation for a resource name, which provides convenient querying methods.
44  *
45  * Also, a `ResourceType` knows how to compute a state change from a particular `ResourceEvent`.
46  *
47  * @author Christos KK Loverdos <loverdos@gmail.com>
48  */
49 sealed abstract class ResourceType(_name: String) {
50   def resourceName = _name
51
52   /**
53    * Return true if the resource type must lead to wallet entries generation and, thus, credit diffs.
54    *
55    * Normally, this should always be the case.
56    */
57   def isBillableType = true
58
59   /**
60    * A resource type is independent if it can, by itself only, create a wallet entry.
61    *
62    * It is dependent if it needs one or more other events of he same type to
63    */
64   def isIndependentType = true
65
66   def isKnownType = true
67   def isDiskSpace = false
68   def isVMTime = false
69   def isBandwidthUpload = false
70   def isBandwidthDownload = false
71
72   /**
73    * Calculates the new `UserState` based on the provided resource event, the calculated wallet entries
74    * and the current `UserState`.
75    *
76    * This method is an implementation detail and is not exposed. The actual user-level API is provided in `ResourceEvent`.
77    */
78   private[events] final
79   def calcStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState): UserState = {
80     val otherState = calcOtherStateChange(resourceEvent, walletEntries, userState)
81     val newCredits = calcNewCreditSnapshot(walletEntries, userState)
82     otherState.copy(credits = newCredits)
83   }
84
85   private[events]
86   def calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState): UserState
87   
88   private[events] final
89   def calcNewCreditSnapshot(walletEntries: List[WalletEntry], userState: UserState): CreditSnapshot = {
90     val newCredits = for {
91       walletEntry <- walletEntries if(walletEntry.finalized)
92     } yield walletEntry.value.toDouble
93
94     val newCreditSum = newCredits.sum
95     val now = System.currentTimeMillis()
96
97     CreditSnapshot(userState.credits.data + newCreditSum, now)
98   }
99 }
100
101 /**
102  * Companion object used to parse a resource name and provide an object representation in the form
103  * of a `ResourceType`.
104  *
105  * Known resource names, which represent Aquarium resources, are like "bndup", "vmtime" etc. and they are all
106  * defined in `ResourceNames`.
107  *
108  * @author Christos KK Loverdos <loverdos@gmail.com>
109  */
110 object ResourceType {
111   def fromName(name: String): ResourceType = {
112     name match {
113       case ResourceNames.bnddown ⇒ BandwidthDown
114       case ResourceNames.bndup   ⇒ BandwidthUp
115       case ResourceNames.vmtime  ⇒ VMTime
116       case _                     ⇒ UnknownResourceType(name)
117     }
118   }
119
120   def fromResourceEvent(resourceEvent: ResourceEvent): ResourceType = fromName(resourceEvent.resource)
121 }
122
123 case object BandwidthDown extends ResourceType(ResourceNames.bnddown) {
124   override def isBandwidthDownload = true
125
126   private[events]
127   def calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState) = {
128     val oldBandwidthDownValue = userState.bandwidthDown.data
129     val bandwidthDownDiff = resourceEvent.value
130
131     val newBandwidth = BandwidthDownSnapshot(oldBandwidthDownValue + bandwidthDownDiff, nowMillis)
132
133     userState.copy(bandwidthDown = newBandwidth)
134   }
135 }
136
137 case object BandwidthUp extends ResourceType(ResourceNames.bndup) {
138   override def isBandwidthUpload = true
139
140   private[events]
141   def calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState) = {
142     val oldBandwidthUpValue = userState.bandwidthUp.data
143     val bandwidthUpDiff = resourceEvent.value
144
145     val newBandwidth = BandwidthUpSnapshot(oldBandwidthUpValue + bandwidthUpDiff, nowMillis)
146
147     userState.copy(bandwidthUp = newBandwidth)
148   }
149 }
150
151 case object VMTime extends ResourceType(ResourceNames.vmtime) {
152   override def isVMTime = true
153
154   override def isIndependentType = false
155
156   def isVMTimeOn(eventDetails: ResourceEvent.Details) = eventDetails.get(ResourceEvent.JsonNames.action) match {
157     case Some("on") ⇒ true
158     case Some("up") ⇒ true
159     case _          ⇒ false
160   }
161   
162   def isVMTimeOff(eventDetails: ResourceEvent.Details) = eventDetails.get(ResourceEvent.JsonNames.action) match {
163     case Some("off")  ⇒ true
164     case Some("down") ⇒ true
165     case _            ⇒ false
166   }
167
168   private[events] def calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState) = {
169     // FIXME: implement
170     userState
171   }
172 }
173
174 case object DiskSpace extends ResourceType(ResourceNames.dsksp) {
175   override def isDiskSpace = true
176
177   private[events] def calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState) = {
178     val oldDiskSpaceValue = userState.diskSpace.data
179     val diskSpaceDiff = resourceEvent.value
180     val newDiskSpace = DiskSpaceSnapshot(oldDiskSpaceValue + diskSpaceDiff, nowMillis)
181     userState.copy(diskSpace = newDiskSpace)
182   }
183 }
184
185 case class UnknownResourceType(originalName: String) extends ResourceType(ResourceNames.unknown) {
186   override def isKnownType = false
187
188   private[events] def
189   calcOtherStateChange(resourceEvent: ResourceEvent, walletEntries: List[WalletEntry], userState: UserState) = userState
190 }