2 * Copyright 2011 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.logic.accounting
38 import dsl.{Timeslot, DSLPolicy, DSL}
39 import gr.grnet.aquarium.Configurator._
40 import java.io.{InputStream, FileInputStream, File}
42 import gr.grnet.aquarium.util.date.TimeHelpers
43 import gr.grnet.aquarium.util.Loggable
44 import java.util.concurrent.atomic.AtomicReference
45 import gr.grnet.aquarium.Configurator
46 import com.ckkloverdos.maybe.{Failed, NoVal, Maybe, Just}
47 import collection.immutable.{TreeMap, SortedMap}
50 * Searches for and loads the applicable accounting policy
52 * @author Georgios Gousios <gousiosg@gmail.com>
54 object Policy extends DSL with Loggable {
56 /* Pointer to the latest policy */
57 private[logic] lazy val policies = {
58 new AtomicReference[SortedMap[Timeslot, DSLPolicy]](reloadPolicies)
61 /* Pointer to the latest policy */
62 private lazy val currentPolicy = {new AtomicReference[DSLPolicy](latestPolicy)}
64 /* Configurator to use for loading information about the policy store */
65 private var config: Configurator = _
68 * Get the latest defined policy.
70 def policy = currentPolicy.get
73 * Get the policy that is valid at the specified time instance.
75 def policy(at: Date): DSLPolicy = {
77 a => (a._1.from.before(at) && a._1.to.after(at)) ||
78 (a._1.from.before(at) && a._1.to == Long.MaxValue)
82 throw new AccountingException("No valid policy for date: %s".format(at))
87 * Get the policies that are valid between the specified time instances,
88 * in a map whose keys are sorted by time.
90 def policies(from: Date, to: Date): SortedMap[Timeslot, DSLPolicy] = {
92 a => a._1.from.before(from) &&
98 * Get the policies that are valid throughout the specified
99 * [[gr.grnet.aquarium.logic.accounting.dsl.Timeslot]]
101 def policies(t: Timeslot): SortedMap[Timeslot, DSLPolicy] = policies(t.from, t.to)
104 * Load and parse a policy from file.
106 def loadPolicyFromFile(pol: File): DSLPolicy = {
108 val stream = pol.exists() match {
110 logger.info("Using policy file %s".format(pol.getAbsolutePath))
111 new FileInputStream(pol)
113 logger.warn(("Cannot find user configured policy file %s, " +
114 "looking for default policy").format(pol.getAbsolutePath))
115 getClass.getClassLoader.getResourceAsStream("policy.yaml") match {
116 case x: InputStream =>
117 logger.warn("Using default policy, this is problably not what you want")
120 logger.error("No valid policy file found, Aquarium will fail")
128 * Trigger a policy update cycle.
130 def updatePolicies = synchronized {
131 //XXX: The following update should happen as one transaction
132 val tmpPol = reloadPolicies
133 currentPolicy.set(latestPolicy)
138 * Find the latest policy in the list of policies.
140 private def latestPolicy =
141 policies.get.foldLeft((Timeslot(new Date(1), new Date(2)) -> DSLPolicy.emptyPolicy)) {
143 if (acc._2 == DSLPolicy.emptyPolicy)
145 else if (p._1.after(acc._1))
152 * Set the configurator to use for loading policy stores. Should only
153 * used for unit testing.
155 /*private[logic] */def withConfigurator(config: Configurator): Unit =
159 * Search default locations for a policy file and provide an
160 * arbitrary default if this cannot be found.
162 private[logic] def policyFile = {
163 BasicResourceContext.getResource(PolicyConfName) match {
164 case Just(policyResource) ⇒
165 val path = policyResource.url.getPath
168 new File("policy.yaml")
173 * Check whether the policy definition file (in whichever path) is
174 * newer than the latest stored policy, reload and set it as current.
175 * This method has side-effects to this object's state.
177 private[logic] def reloadPolicies: SortedMap[Timeslot, DSLPolicy] =
179 reloadPolicies(MasterConfigurator)
181 reloadPolicies(config)
183 private def reloadPolicies(config: Configurator):
184 SortedMap[Timeslot, DSLPolicy] = {
185 //1. Load policies from db
186 val pol = config.policyStore.loadPolicies(0)
188 //2. Check whether policy file has been updated
189 val latestPolicyChange = if (pol.isEmpty) 0 else pol.last.validFrom
190 val policyf = policyFile
193 if (policyf.exists) {
194 if (policyf.lastModified > latestPolicyChange) {
195 logger.info("Policy file updated since last check, reloading")
198 logger.info("Policy file not changed since last check")
201 logger.warn("User specified policy file %s does not exist, " +
202 "using stored policy information".format(policyf.getAbsolutePath))
206 val ts = TimeHelpers.nowMillis
207 val parsedNew = loadPolicyFromFile(policyf)
208 val newPolicy = parsedNew.toPolicyEntry.copy(occurredMillis = ts,
209 receivedMillis = ts, validFrom = ts)
211 config.policyStore.findPolicy(newPolicy.id) match {
213 logger.warn("Policy file contents not modified")
216 val toUpdate = pol.last.copy(validTo = ts - 1)
217 config.policyStore.updatePolicy(toUpdate)
218 config.policyStore.storePolicy(newPolicy)
220 config.policyStore.storePolicy(newPolicy)
222 case Failed(e, expl) =>
227 config.policyStore.loadPolicies(0).foldLeft(new TreeMap[Timeslot, DSLPolicy]()){
229 acc ++ Map(Timeslot(new Date(p.validFrom), new Date(p.validTo)) -> parse(p.policyYAML))