import java.io.{InputStreamReader, InputStream}
import gr.grnet.aquarium.util.Loggable
import gr.grnet.aquarium.util.yaml._
-import org.yaml.snakeyaml.Yaml
-import java.lang.StringBuffer
import java.util.Date
/**
- * A parser and semantic analyser for credit DSL files
+ * A parser and semantic analyser for the Aquarium accounting DSL.
*
* @author Georgios Gousios <gousiosg@gmail.com>
*/
+trait DSL extends Loggable {
-object DSL extends Loggable {
+ /** An empty policy */
+ val emptyPolicy = DSLPolicy("", None, Map(), DSLTimeFrame(new Date(0), None, Option(List())))
- private val emptyPolicy = DSLPolicy("", None, Map(),
- DSLTimeFrame(new Date(0), None, Option(List())))
+ /** An empty pricelist */
+ val emptyPriceList = DSLPriceList("", None, Map(), DSLTimeFrame(new Date(0), None, Option(List())))
- private val emptyPriceList = DSLPriceList("", None, Map(),
- DSLTimeFrame(new Date(0), None, Option(List())))
-
- private val emptyAgreement = DSLAgreement("", None, emptyPolicy,
- emptyPriceList)
+ /** An empty agreement*/
+ val emptyAgreement = DSLAgreement("", None, emptyPolicy, emptyPriceList)
+ /**
+ * Parse an InputStream containing an Aquarium DSL policy.
+ */
def parse(input: InputStream) : DSLCreditPolicy = {
logger.debug("Policy parsing started")
val document = YAMLHelpers.loadYAML(new InputStreamReader(input))
val policy = document / (Vocabulary.creditpolicy)
- val resources = parseResources (policy./(Vocabulary.resources).asInstanceOf[YAMLListNode])
+ val resources = parseResources(policy./(Vocabulary.resources).asInstanceOf[YAMLListNode])
logger.debug("Resources: %s".format(resources))
- val policies = parsePolicies(
- policy./(Vocabulary.policies).asInstanceOf[YAMLListNode],
- resources, List())
+ val policies = parsePolicies(policy./(Vocabulary.policies).asInstanceOf[YAMLListNode],resources, List())
logger.debug("Policies: %s".format(policies))
val pricelists = parsePriceLists(
}
/** Parse top level resources declarations */
- def parseResources(resources: YAMLListNode): List[DSLResource] = {
+ private def parseResources(resources: YAMLListNode): List[DSLResource] = {
if (resources.isEmpty)
return List()
resources.head match {
case x: YAMLStringNode =>
List(DSLResource(x.string)) ++ parseResources(resources.tail)
case _ =>
- throw new DSLParseException("Resource not string:%s".format(resources.head))
+ throw new DSLParseException("Resource not a string:%s".format(resources.head))
}
}
/** Parse top level policy declarations */
- def parsePolicies(policies: YAMLListNode,
+ private def parsePolicies(policies: YAMLListNode,
resources: List[DSLResource],
results: List[DSLPolicy]): List[DSLPolicy] = {
case y: YAMLStringNode =>
results.find(p => p.name.equals(y.string)) match {
case Some(x) => x
- case None => throw new DSLParseException(
- "Cannot find super policy %s".format(superName))
+ case None => throw new DSLParseException("Cannot find super policy %s".format(superName))
}
case YAMLEmptyNode => emptyPolicy
- case _ => throw new DSLParseException(
- "Super policy name %s not a string".format())
+ case _ => throw new DSLParseException("Super policy name %s not a string".format())
}
val policy = constructPolicy(policies.head.asInstanceOf[YAMLMapNode],
}
/** Construct a policy object from a yaml node*/
- private def constructPolicy(policy: YAMLMapNode,
+ def constructPolicy(policy: YAMLMapNode,
policyTmpl: DSLPolicy,
resources: List[DSLResource]): DSLPolicy = {
val name = policy / Vocabulary.name match {
}
val overr = policy / Vocabulary.overrides match {
- case x: YAMLStringNode => Some(x.string)
+ case x: YAMLStringNode => Some(policyTmpl)
case YAMLEmptyNode => None
}
case y: YAMLIntNode => y.int.toString
case YAMLEmptyNode => policyTmpl.equals(emptyPolicy) match {
case false => policyTmpl.algorithms.getOrElse(r,
- throw new DSLParseException(("Severe! Superpolicy does not " +
- "specify an algorithm for resource:%s").format(r.name)))
- case true => throw new DSLParseException(("Cannot find " +
- "calculation algorithm for resource %s in either policy %s or a" +
- " superpolicy").format(r.name, name))
+ throw new DSLParseException(("Superpolicy does not specify an algorithm for resource:%s").format(r.name)))
+ case true => throw new DSLParseException(("Cannot find calculation algorithm for resource %s in either " +
+ "policy %s or a superpolicy").format(r.name, name))
}
}
Map(r -> algo)
case x: YAMLMapNode => parseTimeFrame(x)
case YAMLEmptyNode => policyTmpl.equals(emptyPolicy) match {
case false => policyTmpl.effective
- case true => throw new DSLParseException(("Cannot find effectivity " +
- "period for policy %s ").format(name))
+ case true => throw new DSLParseException(("Cannot find effectivity period for policy %s ").format(name))
}
}
}
/** Parse top level pricelist declarations */
- def parsePriceLists(pricelists: YAMLListNode,
+ private def parsePriceLists(pricelists: YAMLListNode,
resources: List[DSLResource],
results: List[DSLPriceList]): List[DSLPriceList] = {
pricelists.head match {
case y: YAMLStringNode =>
results.find(p => p.name.equals(y.string)) match {
case Some(x) => x
- case None => throw new DSLParseException(
- "Cannot find super pricelist %s".format(superName))
+ case None => throw new DSLParseException("Cannot find super pricelist %s".format(superName))
}
case YAMLEmptyNode => emptyPriceList
- case _ => throw new DSLParseException(
- "Super pricelist name %s not a string".format())
+ case _ => throw new DSLParseException("Super pricelist name %s not a string".format())
}
val pl = constructPriceList(pricelists.head.asInstanceOf[YAMLMapNode],
resources: List[DSLResource]): DSLPriceList = {
val name = pl / Vocabulary.name match {
case x: YAMLStringNode => x.string
- case YAMLEmptyNode => throw new DSLParseException("Policy does not have a name")
+ case YAMLEmptyNode => throw new DSLParseException(
+ "Policy does not have a name")
}
val overr = pl / Vocabulary.overrides match {
- case x: YAMLStringNode => Some(x.string)
+ case x: YAMLStringNode => Some(tmpl)
case YAMLEmptyNode => None
}
case z: YAMLDoubleNode => z.double.toString
case YAMLEmptyNode => tmpl.equals(emptyPolicy) match {
case false => tmpl.prices.getOrElse(r,
- throw new DSLParseException(("Severe! Superpolicy does not " +
- "specify a price for resource:%s").format(r.name)))
- case true => throw new DSLParseException(("Cannot find " +
- "price for resource %s in either pricelist %s or its" +
- " super pricelist").format(r.name, name))
+ throw new DSLParseException(("Superpolicy does not specify a price for resource:%s").format(r.name)))
+ case true => throw new DSLParseException(("Cannot find price for resource %s in either pricelist %s or " +
+ "its super pricelist").format(r.name, name))
}
}
Map(r -> algo)
case x: YAMLMapNode => parseTimeFrame(x)
case YAMLEmptyNode => tmpl.equals(emptyPolicy) match {
case false => tmpl.effective
- case true => throw new DSLParseException(("Cannot find effectivity " +
- "period for pricelist %s ").format(name))
+ case true => throw new DSLParseException(("Cannot find effectivity period for pricelist %s ").format(name))
}
}
DSLPriceList(name, overr, Map(), timeframe)
}
/** Parse top level agreements */
- def parseAgreements(agreements: YAMLListNode,
+ private def parseAgreements(agreements: YAMLListNode,
policies: List[DSLPolicy],
pricelists: List[DSLPriceList],
resources: List[DSLResource],
case y: YAMLStringNode =>
results.find(p => p.name.equals(y.string)) match {
case Some(x) => x
- case None => throw new DSLParseException(
- "Cannot find super agreement %s".format(superName))
+ case None => throw new DSLParseException("Cannot find super agreement %s".format(superName))
}
case YAMLEmptyNode => emptyAgreement
- case _ => throw new DSLParseException(
- "Super agreement name %s not a string".format(superName))
+ case _ => throw new DSLParseException("Super agreement name %s not a string".format(superName))
}
val agr = constructAgreement(agreements.head.asInstanceOf[YAMLMapNode],
resources: List[DSLResource]) : DSLAgreement = {
val name = agr / Vocabulary.name match {
case x: YAMLStringNode => x.string
- case YAMLEmptyNode => throw new DSLParseException("Agreement does not " +
- "have a name")
+ case YAMLEmptyNode => throw new DSLParseException("Agreement does not have a name")
}
val policy = agr / Vocabulary.policy match {
case x: YAMLStringNode => policies.find(p => p.name.equals(x.string)) match {
case Some(y) => y
- case None => throw new DSLParseException(("Cannot find policy " +
- "named %s").format(x))
+ case None => throw new DSLParseException(("Cannot find policy named %s").format(x))
}
case y: YAMLMapNode => tmpl.equals(emptyAgreement) match {
- case true => throw new DSLParseException(("Incomplete policy " +
- "definition for agreement %s").format(name))
+ case true => throw new DSLParseException(("Incomplete policy definition for agreement %s").format(name))
case false =>
y.map += ("name" -> YAMLStringNode("/","%s-policy".format(name)))
constructPolicy(y, tmpl.policy, resources)
}
case YAMLEmptyNode => tmpl.equals(emptyAgreement) match {
- case true => throw new DSLParseException(("No policy " +
- "for agreement %s").format(name))
+ case true => throw new DSLParseException(("No policy for agreement %s").format(name))
case false => tmpl.policy
}
}
val pricelist = agr / Vocabulary.pricelist match {
case x: YAMLStringNode => pricelists.find(p => p.name.equals(x.string)) match {
case Some(y) => y
- case None => throw new DSLParseException(("Cannot find pricelist " +
- "named %s").format(x))
+ case None => throw new DSLParseException(("Cannot find pricelist named %s").format(x))
}
case y: YAMLMapNode => tmpl.equals(emptyAgreement) match {
- case true => throw new DSLParseException(("Incomplete pricelist " +
- "definition for agreement %s").format(name))
+ case true => throw new DSLParseException(("Incomplete pricelist definition for agreement %s").format(name))
case false =>
y.map += ("name" -> YAMLStringNode("/","%s-pricelist".format(name)))
constructPriceList(y, tmpl.pricelist, resources)
}
case YAMLEmptyNode => tmpl.equals(emptyAgreement) match {
- case true => throw new DSLParseException(("No policy " +
- "for agreement %s").format(name))
+ case true => throw new DSLParseException(("No policy for agreement %s").format(name))
case false => tmpl.pricelist
}
}
def parseTimeFrame(timeframe: YAMLMapNode): DSLTimeFrame = {
val from = timeframe / Vocabulary.from match {
case x: YAMLIntNode => new Date(x.int)
- case _ => throw new DSLParseException(
- "No %s field for timeframe %s".format(Vocabulary.from, timeframe))
+ case _ => throw new DSLParseException("No %s field for timeframe %s".format(Vocabulary.from, timeframe))
}
val to = timeframe / Vocabulary.to match {
}
/** Parse a time frame entry (start, end tags) */
- private def findInMap(repeat: YAMLMapNode, tag: String) : List[DSLCronSpec] = {
+ private def findInMap(repeat: YAMLMapNode,
+ tag: String) : List[DSLCronSpec] = {
repeat / tag match {
case x: YAMLStringNode => parseCronString(x.string)
- case YAMLEmptyNode => throw new DSLParseException(
- "No %s field for repeat entry %s".format(tag, repeat))
+ case YAMLEmptyNode => throw new DSLParseException("No %s field for repeat entry %s".format(tag, repeat))
}
}
/**
- * Wraps the crontabparser library to parse DSL formatted cron strings.
+ * Wraps the [[http://kenai.com/projects/crontab-parser/pages/Home crontabparser]]
+ * library to parse crontab-like strings. The input format differs from the
+ * [[http://en.wikipedia.org/wiki/Cron default cron format]] in the following ways:
+ *
+ * - Only 5 field time specs are allowed
+ * - Multiple values per field (e.g. Mon,Wed,Fri) are not allowed. Ranges
+ * (e.g. Mon-Fri) are however allowed.
+ *
*/
def parseCronString(input: String): List[DSLCronSpec] = {
if (input.split(" ").length != 5)
- throw new DSLParseException(
- "Only five-field cron strings allowed: " + input)
+ throw new DSLParseException("Only five-field cron strings allowed: " + input)
if (input.contains(','))
- throw new DSLParseException(
- "Multiple values per field are not allowed: " + input)
+ throw new DSLParseException("Multiple values per field are not allowed: " + input)
val cron = try {
asScalaBuffer(CronTabParserBridge.parse(input))
} catch {
- case e => throw new DSLParseException(
- "Error parsing cron string: " + e.getMessage)
+ case e => throw new DSLParseException("Error parsing cron string: " + e.getMessage)
}
def splitMultiVals(input: String): Range = {
).flatten.toList
}
- /** Merge two pricelists, field by field */
- def mergePolicy(policy: DSLPolicy, onto: DSLPolicy): DSLPolicy = {
- DSLPolicy(onto.name, onto.overrides,
- mergeMaps(policy.algorithms, onto.algorithms),
- mergeTimeFrames(policy.effective, onto.effective))
- }
-
- /** Merge two timeframes */
- def mergeTimeFrames(timeframe: DSLTimeFrame,
- onto: DSLTimeFrame) : DSLTimeFrame = {
-
- val to = timeframe.to match {
- case Some(x) => timeframe.to
- case None => onto.to
- }
-
- val eff = timeframe.repeat match {
- case None => onto.repeat
- case Some(x) if x == Nil => onto.repeat
- case _ => timeframe.repeat
- }
-
- DSLTimeFrame(timeframe.from, to, eff)
- }
-
/** Merge input maps on a field by field basis. In case of duplicate keys
* values from the first map are prefered.
*/
else kv)
}
- /*Functions to search credit policy by name*/
def findResource(policy: DSLCreditPolicy, name: String) : Option[DSLResource] = {
policy.resources.find(a => a.name.equals(name))
}
}
-sealed abstract class DSLTreeNode {
- def toYaml() : String
- def children() : List[DSLTreeNode]
-}
-
case class DSLCreditPolicy (
policies: List[DSLPolicy],
pricelists: List[DSLPriceList],
case class DSLPolicy (
name: String,
- overrides: Option[String],
+ overrides: Option[DSLPolicy],
algorithms: Map[DSLResource, String],
effective: DSLTimeFrame
)
case class DSLPriceList (
name: String,
- overrides: Option[String],
+ overrides: Option[DSLPriceList],
prices: Map[DSLResource, Float],
effective: DSLTimeFrame
)
import org.junit.{Test}
import java.util.Date
-class DSLTest {
+class DSLTest extends DSL {
var creditpolicy : DSLCreditPolicy = _
def before = {
- creditpolicy = DSL.parse(
+ creditpolicy = parse(
getClass.getClassLoader.getResourceAsStream("policy.yaml")
)
assertNotNull(creditpolicy)
assertEquals(creditpolicy.policies(1).algorithms.size,
creditpolicy.resources.size)
- val d = DSL.findResource(creditpolicy, "diskspace").get
+ val d = findResource(creditpolicy, "diskspace").get
assertNotNone(d)
assertNotSame(creditpolicy.policies(0).algorithms(d),
}
@Test
- def testMergePolicies = {
- val vm = DSLResource("vmtime")
- val bup = DSLResource("bup")
- val bdown = DSLResource("bdown")
-
- val a = DSLPolicy(
- "1", Some("2"),
- Map(vm -> "abc", bup -> "def"),
- DSLTimeFrame(
- new Date(0), Option(new Date(12345)), Some(List(
- DSLTimeFrameRepeat(
- List(DSLCronSpec(12, 34, 2, -1, -1)),
- List(DSLCronSpec(12, 34, 4, -1, -1))
- ))
- )
- )
- )
- val b = DSLPolicy("2", Some(""),
- Map(vm -> "xyz", bdown -> "foo"),
- DSLTimeFrame(new Date(0), Option(new Date(45678)), Option(List()))
- )
-
- val result = DSL.mergePolicy(a, b)
-
- assertEquals(result.name, "2")
- assertEquals(result.algorithms.size, 3)
- assertEquals(result.algorithms.get(vm), Some("abc"))
- assertEquals(result.algorithms.get(bup), Some("def"))
- assertEquals(result.algorithms.get(bdown), Some("foo"))
- assertEquals(1, result.effective.repeat.size)
- }
-
- @Test
- def testMergeTimeframes = {
- val a = DSLTimeFrame(
- new Date(0), Option(new Date(12345)), Some(List(
- DSLTimeFrameRepeat(
- List(DSLCronSpec(12, 34, 2, -1, -1)),
- List(DSLCronSpec(12, 34, 4, -1, -1))
- ))
- )
- )
- val b = DSLTimeFrame(new Date(0), Option(new Date(45678)), Some(List()))
-
- var result = DSL.mergeTimeFrames(a, b)
- assertEquals(a.from, result.from)
- assertEquals(a.to, result.to)
- assertEquals(1, result.repeat.get.size)
-
- result = DSL.mergeTimeFrames(b, a)
- assertEquals(b.from, result.from)
- assertEquals(b.to, result.to)
- assertEquals(1, result.repeat.get.size)
- }
-
- @Test
def testCronParse = {
var input = "12 * * * *"
- var output = DSL.parseCronString(input)
+ var output = parseCronString(input)
assertEquals(output, List(DSLCronSpec(12, -1, -1, -1, -1)))
input = "12 4 3 jaN-ApR *"
- output = DSL.parseCronString(input)
+ output = parseCronString(input)
assertEquals(output.size, 4)
assertEquals(output(2), DSLCronSpec(12, 4, 3, 3, -1))
input = "12 4 3 jaN-ApR MOn-FRi"
- output = DSL.parseCronString(input)
+ output = parseCronString(input)
assertEquals(output.size, 20)
input = "12 4 foo jaN-ApR *"
- assertThrows(DSL.parseCronString(input))
+ assertThrows(parseCronString(input))
input = "12 4 foo jaN-ApR 9"
- assertThrows(DSL.parseCronString(input))
+ assertThrows(parseCronString(input))
input = "@midnight"
- assertThrows(DSL.parseCronString(input))
+ assertThrows(parseCronString(input))
}
@Test
def testSerialization = {
before
- val a = creditpolicy.toYaml()
- println(a)
}
def assertThrows(f: => Unit) = {