Minor fix in BillEntry (Virtual Resource event have no file path).
[aquarium] / src / main / scala / gr / grnet / aquarium / charging / bill / BillEntry.scala
1 package gr.grnet.aquarium.charging.bill
2
3 import gr.grnet.aquarium.charging.state.WorkingUserState
4 import gr.grnet.aquarium.util.json.JsonSupport
5 import com.ckkloverdos.resource.FileStreamResource
6 import java.io.File
7 import com.ckkloverdos.props.Props
8 import gr.grnet.aquarium.converter.{CompactJsonTextFormat, PrettyJsonTextFormat, StdConverters}
9 import gr.grnet.aquarium.{Aquarium, ResourceLocator, AquariumBuilder}
10 import gr.grnet.aquarium.store.memory.MemStoreProvider
11 import gr.grnet.aquarium.converter.StdConverters._
12 import scala._
13 import gr.grnet.aquarium.logic.accounting.dsl.Timeslot
14 import java.util.concurrent.atomic.AtomicLong
15 import java.util.{Date, Calendar, GregorianCalendar}
16 import gr.grnet.aquarium.charging.wallet.WalletEntry
17 import scala.collection.parallel.mutable
18 import scala.collection.mutable.ListBuffer
19 import gr.grnet.aquarium.Aquarium.EnvKeys
20 import gr.grnet.aquarium.charging.Chargeslot
21 import scala.collection.immutable.TreeMap
22 import scala.Some
23 import gr.grnet.aquarium.charging.Chargeslot
24
25
26 /*
27 * Copyright 2011-2012 GRNET S.A. All rights reserved.
28 *
29 * Redistribution and use in source and binary forms, with or
30 * without modification, are permitted provided that the following
31 * conditions are met:
32 *
33 *   1. Redistributions of source code must retain the above
34 *      copyright notice, this list of conditions and the following
35 *      disclaimer.
36 *
37 *   2. Redistributions in binary form must reproduce the above
38 *      copyright notice, this list of conditions and the following
39 *      disclaimer in the documentation and/or other materials
40 *      provided with the distribution.
41 *
42 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
43 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
44 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
45 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
46 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
48 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
49 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
50 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
51 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
52 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
53 * POSSIBILITY OF SUCH DAMAGE.
54 *
55 * The views and conclusions contained in the software and
56 * documentation are those of the authors and should not be
57 * interpreted as representing official policies, either expressed
58 * or implied, of GRNET S.A.
59 */
60
61
62 /*
63 * @author Prodromos Gerakios <pgerakios@grnet.gr>
64 */
65
66 case class ChargeEntry(val id:String,
67                        val unitPrice:String,
68                        val startTime:String,
69                        val endTime:String,
70                        val ellapsedTime:String,
71                        val credits:String)
72   extends JsonSupport {}
73
74
75 class EventEntry(val eventType : String,
76                  val details   : List[ChargeEntry])
77  extends JsonSupport {}
78
79
80 case class ResourceEntry(val resourceName : String,
81                          val resourceType : String,
82                          val unitName : String,
83                          val totalCredits : String,
84                          val details : List[EventEntry])
85 extends JsonSupport {}
86
87
88 abstract class AbstractBillEntry
89  extends JsonSupport {}
90
91 class BillEntry(val id:String,
92                 val userID : String,
93                 val status : String,
94                 val remainingCredits:String,
95                 val deductedCredits:String,
96                 val startTime:String,
97                 val endTime:String,
98                 val bill:List[ResourceEntry]
99               )
100  extends AbstractBillEntry {}
101
102
103 object AbstractBillEntry {
104
105   private[this] val counter = new AtomicLong(0L)
106   private[this] def nextUIDObject() = counter.getAndIncrement
107
108   /*private[this] def walletTimeslot(i:WalletEntry) : Timeslot = {
109     val cal = new GregorianCalendar
110     cal.set(i.billingYear,i.billingMonth,1)
111     val dstart = cal.getTime
112     val lastDate = cal.getActualMaximum(Calendar.DATE)
113     cal.set(Calendar.DATE, lastDate)
114     val dend = cal.getTime
115    Timeslot(dstart,dend)
116   } */
117
118   private[this] def toChargeEntry(c:Chargeslot) : ChargeEntry = {
119     val unitPrice = c.unitPrice.toString
120     val startTime = c.startMillis.toString
121     val endTime   = c.stopMillis.toString
122     val difTime   = (c.stopMillis - c.startMillis).toString
123     val credits   = c.creditsToSubtract.toString
124     new ChargeEntry(counter.getAndIncrement.toString,unitPrice,
125                     startTime,endTime,difTime,credits)
126   }
127
128   private[this] def toEventEntry(eventType:String,c:Chargeslot) : EventEntry =
129     new EventEntry(eventType,List(toChargeEntry(c)))
130
131
132   private[this] def toResourceEntry(w:WalletEntry) : ResourceEntry = {
133     assert(w.sumOfCreditsToSubtract==0.0 || w.chargslotCount > 0)
134     val rcType =  w.resourceType.name
135     val rcName = rcType match {
136             case "diskspace" =>
137               w.currentResourceEvent.details.get("path") match {
138                 case Some(path) => path
139                 case None =>
140                   w.currentResourceEvent.instanceID
141               }
142             case _ =>
143               w.currentResourceEvent.instanceID
144         }
145     val rcUnitName = w.resourceType.unit
146     val eventEntry = new ListBuffer[EventEntry]
147     val credits = w.sumOfCreditsToSubtract
148     val eventType = //TODO: This is hardcoded; find a better solution
149         rcType match {
150           case "diskspace" =>
151             val action = w.currentResourceEvent.details("action")
152             val path = w.currentResourceEvent.details("path")
153             //"%s@%s".format(action,path)
154             action
155           case "vmtime" =>
156             w.currentResourceEvent.value.toInt match {
157               case 0 => // OFF
158                   "offOn"
159               case 1 =>  // ON
160                  "onOff"
161               case 2 =>
162                  "destroy"
163               case _ =>
164                  "BUG"
165             }
166           case "addcredits" =>
167             "once"
168         }
169
170     for { c <- w.chargeslots }{
171       if(c.creditsToSubtract != 0.0) {
172         //Console.err.println("c.creditsToSubtract : " + c.creditsToSubtract)
173         eventEntry += toEventEntry(eventType.toString,c)
174         //credits += c.creditsToSubtract
175       }
176     }
177     //Console.err.println("TOTAL resource event credits: " + credits)
178     new ResourceEntry(rcName,rcType,rcUnitName,credits.toString,eventEntry.toList)
179   }
180
181   private[this] def resourceEntriesAt(t:Timeslot,w:WorkingUserState) : (List[ResourceEntry],Double) = {
182     val ret = new ListBuffer[ResourceEntry]
183     var sum = 0.0
184     //Console.err.println("Wallet entries: " + w.walletEntries)
185     val walletEntries = w.walletEntries
186     /*Console.err.println("Wallet entries ")
187     for { i <- walletEntries }
188       Console.err.println("WALLET ENTRY\n%s\nEND WALLET ENTRY".format(i.toJsonString))
189     Console.err.println("End wallet entries")*/
190     for { i <- walletEntries} {
191       if(t.contains(i.referenceTimeslot) && i.sumOfCreditsToSubtract != 0.0){
192         /*Console.err.println("i.sumOfCreditsToSubtract : " + i.sumOfCreditsToSubtract)*/
193         if(i.sumOfCreditsToSubtract > 0.0D) sum += i.sumOfCreditsToSubtract
194         ret += toResourceEntry(i)
195       } else {
196         /*Console.err.println("WALLET ENTERY : " + i.toJsonString + "\n" +
197                      t + "  does not contain " +  i.referenceTimeslot + "  !!!!")*/
198       }
199     }
200     (ret.toList,sum)
201   }
202
203   private[this] def aggregateResourceEntries(re:List[ResourceEntry]) : List[ResourceEntry] = {
204     def addResourceEntries(a:ResourceEntry,b:ResourceEntry) : ResourceEntry = {
205       assert(a.resourceName == b.resourceName)
206       val totalCredits = (a.totalCredits.toDouble+b.totalCredits.toDouble).toString
207       a.copy(a.resourceName,a.resourceType,a.unitName,totalCredits,a.details ::: b.details)
208     }
209     re.foldLeft(TreeMap[String,ResourceEntry]()){ (map,r1) =>
210       map.get(r1.resourceName) match {
211         case None => map + ((r1.resourceName,r1))
212         case Some(r0) => (map - r0.resourceName) +
213                          ((r0.resourceName, addResourceEntries(r0,r1)))
214       }
215     }.values.toList
216   }
217
218   def fromWorkingUserState(t:Timeslot,userID:String,w:Option[WorkingUserState]) : AbstractBillEntry = {
219     val ret = w match {
220       case None =>
221           new BillEntry(counter.getAndIncrement.toString,
222                         userID,"processing",
223                         "0.0",
224                         "0.0",
225                         t.from.getTime.toString,t.to.getTime.toString,
226                         Nil)
227       case Some(w) =>
228         val (rcEntries,rcEntriesCredits) = resourceEntriesAt(t,w)
229         val resMap = aggregateResourceEntries(rcEntries)
230         Console.err.println("Working user state: %s".format(w.toString))
231         new BillEntry(counter.getAndIncrement.toString,
232                       userID,"ok",
233                       w.totalCredits.toString,
234                       rcEntriesCredits.toString,
235                       t.from.getTime.toString,t.to.getTime.toString,
236                       resMap)
237     }
238     //Console.err.println("JSON: " +  ret.toJsonString)
239     ret
240   }
241
242   val jsonSample = "{\n  \"id\":\"2\",\n  \"userID\":\"loverdos@grnet.gr\",\n  \"status\":\"ok\",\n  \"remainingCredits\":\"3130.0000027777783\",\n  \"deductedCredits\":\"5739.9999944444435\",\n  \"startTime\":\"1341090000000\",\n  \"endTime\":\"1343768399999\",\n  \"bill\":[{\n    \"resourceName\":\"diskspace\",\n    \"resourceType\":\"diskspace\",\n    \"unitName\":\"MB/Hr\",\n    \"totalCredits\":\"2869.9999972222217\",\n    \"eventType\":\"object update@/Papers/GOTO_HARMFUL.PDF\",\n\t    \"details\":[\n\t     {\"totalCredits\":\"2869.9999972222217\",\n\t      \"details\":[{\n\t      \"id\":\"0\",\n\t      \"unitPrice\":\"0.01\",\n\t      \"startTime\":\"1342735200000\",\n\t      \"endTime\":\"1343768399999\",\n\t      \"ellapsedTime\":\"1033199999\",\n\t      \"credits\":\"2869.9999972222217\"\n\t    \t}]\n\t    }\n\t  ]\n  },{\n    \"resourceName\":\"diskspace\",\n    \"resourceType\":\"diskspace\",\n    \"unitName\":\"MB/Hr\",\n    \"totalCredits\":\"2869.9999972222217\",\n    \"eventType\":\"object update@/Papers/GOTO_HARMFUL.PDF\",\n    \"details\":[\t     {\"totalCredits\":\"2869.9999972222217\",\n\t      \"details\":[{\n\t      \"id\":\"0\",\n\t      \"unitPrice\":\"0.01\",\n\t      \"startTime\":\"1342735200000\",\n\t      \"endTime\":\"1343768399999\",\n\t      \"ellapsedTime\":\"1033199999\",\n\t      \"credits\":\"2869.9999972222217\"\n\t    \t}]\n\t    }\n\t]\n  }]\n}"
243
244   def main0(args: Array[String]) = {
245      val b : BillEntry = StdConverters.AllConverters.convertEx[BillEntry](CompactJsonTextFormat(jsonSample))
246      val l0 = b.bill
247      val l1 = aggregateResourceEntries(l0)
248
249      Console.err.println("Initial resources: ")
250      for{ i <- l0 } Console.err.println("RESOURCE: " + i.toJsonString)
251     Console.err.println("Aggregate resources: ")
252     for{ a <- l1 } {
253       Console.err.println("RESOURCE:  %s\n  %s\nEND RESOURCE".format(a.resourceName,a.toJsonString))
254     }
255
256     val aggr = new BillEntry(b.id,b.userID,b.status,b.remainingCredits,b.deductedCredits,b.startTime,b.endTime,l1)
257     Console.err.println("Aggregate:\n" + aggr.toJsonString)
258   }
259
260   //
261   def main(args: Array[String]) = {
262     //Console.err.println("JSON: " +  (new BillEntry).toJsonString)
263     val propsfile = new FileStreamResource(new File("aquarium.properties"))
264     var _props: Props = Props(propsfile)(StdConverters.AllConverters).getOr(Props()(StdConverters.AllConverters))
265     val aquarium = new AquariumBuilder(_props, ResourceLocator.DefaultPolicyModel).
266       update(Aquarium.EnvKeys.storeProvider, new MemStoreProvider).
267       update(Aquarium.EnvKeys.eventsStoreFolder,Some(new File(".."))).
268       build()
269     aquarium.start()
270     ()
271   }
272 }