From: Georgios Gousios Date: Fri, 17 Feb 2012 14:17:49 +0000 (+0200) Subject: Applicability timeslots for policies and timeslots X-Git-Url: https://code.grnet.gr/git/aquarium/commitdiff_plain/fb92babb1d0eb9b6f2c87f04607c104e2621d789 Applicability timeslots for policies and timeslots Charge events by spliting charge time into slices defined by the applicability timeslots for policies and agreements --- diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala index b097f4e..4edbcbc 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/Accounting.scala @@ -127,22 +127,97 @@ trait Accounting extends DSLUtils with Loggable { } }.flatten1 } + /** - * Creates a list of wallet entries by applying the agreement provisions on - * the resource state. + * Creates a list of wallet entries by examining the on the resource state. * * @param currentResourceEvent The resource event to create charges for - * @param agreement The agreement applicable to the user mentioned in the event + * @param agreements The user's agreement names, indexed by their + * applicability timeslot * @param previousAmount The current state of the resource * @param previousOccurred The last time the resource state was updated */ def chargeEvent(currentResourceEvent: ResourceEvent, - agreements: List[String], + agreements: SortedMap[Timeslot, String], previousAmount: Double, previousOccurred: Date, related: List[WalletEntry]): Maybe[List[WalletEntry]] = { assert(previousOccurred.getTime <= currentResourceEvent.occurredMillis) + val occuredDate = new Date(currentResourceEvent.occurredMillis) + + /* The following makes sure that agreements exist between the start + * and end days of the processed event. As agreement updates are + * guaranteed not to leave gaps, this means that the event can be + * processed correctly, as at least one agreement will be valid + * throughout the event's life. + */ + assert( + agreements.keys.exists { + p => p.includes(occuredDate) + } && agreements.keys.exists { + p => p.includes(previousOccurred) + } + ) + + val t = Timeslot(previousOccurred, occuredDate) + + // Align policy and agreement validity timeslots to the event's boundaries + val policyTimeslots = t.align( + Policy.policies(previousOccurred, occuredDate).keys.toList) + val agreementTimeslots = t.align(agreements.keys.toList) + + /* + * Get a set of timeslot slices covering the different durations of + * agreements and policies. + */ + val aligned = alignTimeslots(policyTimeslots, agreementTimeslots) + + val walletEntries = aligned.map { + x => + // Retrieve agreement from policy valid at time of event + val agreementName = agreements.find(y => y._1.contains(x)) match { + case Some(x) => x + case None => return Failed(new AccountingException(("Cannot find" + + " user agreement for period %s").format(x))) + } + + // Do the wallet entry calculation + val entries = chargeEvent( + currentResourceEvent, + Policy.policy(x.from).findAgreement(agreementName._2).getOrElse( + return Failed(new AccountingException("Cannot get agreement for ")) + ), + previousAmount, + previousOccurred, + related + ) match { + case Just(x) => x + case Failed(f, e) => return Failed(f,e) + case NoVal => List() + } + entries + }.flatten + + Just(walletEntries) + } + + /** + * Creates a list of wallet entries by applying the agreement provisions on + * the resource state. + * + * @param currentResourceEvent The resource event to create charges for + * @param agr The agreement implementation to use + * @param previousAmount The current state of the resource + * @param previousOccurred + * @param related + * @return + */ + def chargeEvent(currentResourceEvent: ResourceEvent, + agr: DSLAgreement, + previousAmount: Double, + previousOccurred: Date, + related: List[WalletEntry]): Maybe[List[WalletEntry]] = { if (!currentResourceEvent.validate()) return Failed(new AccountingException("Event not valid")) @@ -193,7 +268,7 @@ trait Accounting extends DSLUtils with Loggable { case _ => Timeslot(previousOccurred, new Date(currentResourceEvent.occurredMillis)) } - val chargeChunks = calcChangeChunks(agreement, amount, dslResource, timeslot) + val chargeChunks = calcChangeChunks(agr, amount, dslResource, timeslot) val timeReceived = System.currentTimeMillis @@ -266,7 +341,7 @@ trait Accounting extends DSLUtils with Loggable { * examines them and splits them as necessary. */ private[logic] def splitChargeChunks(alg: SortedMap[Timeslot, DSLAlgorithm], - price: SortedMap[Timeslot, DSLPriceList]) : + price: SortedMap[Timeslot, DSLPriceList]) : (Map[Timeslot, DSLAlgorithm], Map[Timeslot, DSLPriceList]) = { val zipped = alg.keySet.zip(price.keySet) @@ -293,8 +368,43 @@ trait Accounting extends DSLUtils with Loggable { } } } + + /** + * Given two lists of timeslots, produce a list which contains the + * set of timeslot slices, as those are defined by + * timeslot overlaps. + * + * For example, given the timeslots a and b below, split them as shown. + * + * a = |****************| + * ^ ^ + * a.from a.to + * b = |*********| + * ^ ^ + * b.from b.to + * + * result: List(Timeslot(a.from, b.to), Timeslot(b.to, a.to)) + */ + private[logic] def alignTimeslots(a: List[Timeslot], + b: List[Timeslot]): List[Timeslot] = { + if (a.isEmpty) return b.tail + if (b.isEmpty) return a.tail + assert (a.head.from == b.head.from) + + if (a.head.endsAfter(b.head)) { + a.head.slice(b.head.to) ::: alignTimeslots(a.tail, b.tail) + } else if (b.head.endsAfter(a.head)) { + b.head.slice(a.head.to) ::: alignTimeslots(a.tail, b.tail) + } else { + a.head :: alignTimeslots(a.tail, b.tail) + } + } } +/** + * Encapsulates a computation for a specific timeslot of + * resource usage. + */ case class ChargeChunk(value: Double, algorithm: String, price: Double, when: Timeslot, resource: DSLResource) { diff --git a/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala b/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala index 444e47c..1a3acea 100644 --- a/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala +++ b/src/test/scala/gr/grnet/aquarium/logic/test/AccountingTest.scala @@ -52,6 +52,19 @@ import com.ckkloverdos.maybe.{NoVal, Failed, Just} class AccountingTest extends DSLTestBase with Accounting with TestMethods { @Test + def testAlignTimeslots() { + before + val from = new Date(1322555880000L) //Tue, 29 Nov 2011 10:38:00 EET + val to = new Date(1322689082000L) //Wed, 30 Nov 2011 23:38:02 EET + val agr = dsl.findAgreement("scaledbandwidth").get + val a = resolveEffectiveAlgorithmsForTimeslot(Timeslot(from, to), agr).keys.toList + val b = resolveEffectivePricelistsForTimeslot(Timeslot(from, to), agr).keys.toList + + val result = alignTimeslots(a, b) + assertEquals(12, result.size) + } + + @Test def testSplitChargeChunks() = { before val from = new Date(1322555880000L) //Tue, 29 Nov 2011 10:38:00 EET