package gr.grnet.aquarium.logic.events
-import gr.grnet.aquarium.logic.accounting.Policy
-import net.liftweb.json.{Extraction, parse => parseJson, JsonAST, Xml}
+import net.liftweb.json.{JsonAST, Xml}
import gr.grnet.aquarium.util.json.JsonHelpers
-import gr.grnet.aquarium.MasterConf._
-import com.ckkloverdos.maybe.{Failed, NoVal, Just}
+import gr.grnet.aquarium.logic.accounting.dsl._
+import com.ckkloverdos.maybe.{MaybeOption, Maybe}
+import java.util.Date
+import gr.grnet.aquarium.util.date.DateCalculator
/**
* Event sent to Aquarium by clients for resource accounting.
* @author Georgios Gousios <gousiosg@gmail.com>.
*/
case class ResourceEvent(
- override val id: String,
- userId: String,
- clientId: String,
- resource: String,
- override val timestamp: Long,
- eventVersion: String,
- details: Map[String, String]
-) extends AquariumEvent(id, timestamp) {
+ override val id: String, // The id at the client side (the sender) TODO: Rename to remoteId or something...
+ override val occurredMillis: Long, // When it occurred at client side (the sender)
+ override val receivedMillis: Long, // When it was received by Aquarium
+ userId: String, // The user for which this resource is relevant
+ clientId: String, // The unique client identifier (usually some hash)
+ resource: String, // String representation of the resource type (e.g. "bndup", "vmtime").
+ instanceId: String, // String representation of the resource instance id
+ eventVersion: String,
+ value: Double,
+ details: ResourceEvent.Details)
+ extends AquariumEvent(id, occurredMillis, receivedMillis) {
def validate() : Boolean = {
+ !safeResource.isEmpty
+ }
+
+ def safeResource = if(resource eq null) "" else resource
+ def safeInstanceId = if(instanceId eq null) "" else instanceId
+
+ def hasResource = !safeResource.isEmpty
+ def hasInstanceId = !safeInstanceId.isEmpty
+
+ def fullResourceInfo = (safeResource, safeInstanceId)
+
+ def isOccurredWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+ fromMillis <= occurredMillis && occurredMillis <= toMillis
+ }
+
+ def isOccurredWithinDates(fromDate: Date, toDate: Date): Boolean = {
+ fromDate.getTime <= occurredMillis && occurredMillis <= toDate.getTime
+ }
+
+ def isReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+ fromMillis <= receivedMillis && receivedMillis <= toMillis
+ }
+
+ def isReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
+ fromDate.getTime <= receivedMillis && receivedMillis <= toDate.getTime
+ }
+
+ def isOccurredOrReceivedWithinMillis(fromMillis: Long, toMillis: Long): Boolean = {
+ isOccurredWithinMillis(fromMillis, toMillis) ||
+ isReceivedWithinMillis(fromMillis, toMillis)
+ }
+
+ def isOccurredOrReceivedWithinDates(fromDate: Date, toDate: Date): Boolean = {
+ isOccurredWithinDates(fromDate, toDate) ||
+ isReceivedWithinDates(fromDate, toDate)
+ }
+
+ def toDebugString(resourcesMap: DSLResourcesMap, useOnlyInstanceId: Boolean): String = {
+ val instanceInfo = if(useOnlyInstanceId) instanceId else "%s::%s".format(resource, instanceId)
+ val bvalue = beautifyValue(resourcesMap)
+ val occurredFormatted = new DateCalculator(occurredMillis).toString
+ if(occurredMillis == receivedMillis) {
+ "EVENT(%s, [%s], %s, %s, %s, %s, %s)".format(
+ id,
+ occurredFormatted,
+ instanceInfo,
+ bvalue,
+ details,
+ userId,
+ clientId
+ )
+ } else {
+ "EVENT(%s, [%s], [%s], %s, %s, %s, %s, %s)".format(
+ id,
+ occurredFormatted,
+ new DateCalculator(receivedMillis),
+ instanceInfo,
+ bvalue,
+ details,
+ userId,
+ clientId
+ )
+ }
+ }
- val userState = MasterConf.userStore.findUserStateByUserId(userId) match {
- case Just(x) => x
- case NoVal =>
- logger.warn("Inexistent user for resource event: %s".format(this.toJson))
- return false
- case Failed(x,y) =>
- logger.warn("Error retrieving user state: %s".format(x))
- return false
+ private[this]
+ def beatifyValue(resourceProvider: (String) ⇒ Option[DSLResource]): String = {
+ resourceProvider(this.resource) match {
+ case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+ OnOffPolicyResourceState(this.value).state.toUpperCase
+ case Some(rc @ DSLComplexResource(_, _, _, _)) ⇒
+ "%s [%s]".format(value, rc.unit)
+ case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+ OnOffPolicyResourceState(this.value).state.toUpperCase
+ case Some(rc @ DSLSimpleResource(_, _, _)) ⇒
+ "%s [%s]".format(value, rc.unit)
+ case _ ⇒
+ value.toString
}
+ }
+
+ /**
+ * Returns a beautiful string representation of the value.
+ *
+ * @param policy The policy to be asked for resources.
+ * @return A beautiful string representation of the value.
+ */
+ def beautifyValue(policy: DSLPolicy): String = {
+ beatifyValue(policy.findResource)
+ }
+
+ /**
+ * Returns a beautiful string representation of the value.
+ *
+ * @param resourcesMap The resources map to be asked for resources.
+ * @return A beautiful string representation of the value.
+ */
+ def beautifyValue(resourcesMap: DSLResourcesMap): String = {
+ beatifyValue(resourcesMap.findResource)
+ }
- //TODO: Get agreement for ev.userid
- val agreement = userState.agreement.data.name
+ /**
+ * Return `true` iff this is an event regarding a resource with an
+ * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]].
+ */
+ def isOnOffEvent(policy: DSLPolicy): Boolean = {
+ policy.findResource(this.resource).map(_.costPolicy) match {
+ case Some(OnOffCostPolicy) ⇒ true
+ case _ ⇒ false
+ }
+ }
- val agr = Policy.policy.findAgreement(agreement) match {
- case Some(x) => x
- case None => return false
+ /**
+ * Return `true` iff this is an event regarding a resource with an
+ * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
+ * `value` of `"on"`.
+ */
+ def isOnEvent(policy: DSLPolicy): Boolean = {
+ policy.findResource(this.resource) match {
+ case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+ OnOffPolicyResourceState(this.value).isOn
+ case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+ OnOffPolicyResourceState(this.value).isOn
+ case _ ⇒
+ false
}
+ }
- val res = Policy.policy.findResource(resource) match {
- case Some(x) => x
- case None => return false
+ /**
+ * Return `true` iff this is an event regarding a resource with an
+ * [[gr.grnet.aquarium.logic.accounting.dsl.OnOffCostPolicy]] and a
+ * `value` of `"off"`.
+ */
+ def isOffEvent(policy: DSLPolicy): Boolean = {
+ policy.findResource(this.resource) match {
+ case Some(DSLComplexResource(_, _, OnOffCostPolicy, _)) ⇒
+ OnOffPolicyResourceState(this.value).isOff
+ case Some(DSLSimpleResource(_, _, OnOffCostPolicy)) ⇒
+ OnOffPolicyResourceState(this.value).isOff
+ case _ ⇒
+ false
}
+ }
- if (!details.keySet.contains("value"))
- return false
+ def copyWithReceivedMillis(millis: Long) = copy(receivedMillis = millis)
- //if (resource.complex && !details.keySet.contains("instance-id"))
- // return false
- //TODO: See how to describe complex resources
+ /**
+ * Find the cost policy of the resource named in this resource event.
+ *
+ * We do not expect cost policies for resources to change, because they are supposed
+ * to be one of their constant characteristics. That is why do not issue a time-dependent
+ * query here for the event's current policy.
+ *
+ * Should the need arises to change the cost policy for a resource, this is a good enough
+ * reason to consider creating another type of resource.
+ */
+ def findCostPolicyM(defaultPolicy: DSLPolicy): Maybe[DSLCostPolicy] = {
+ defaultPolicy.findResource(this.safeResource).map(_.costPolicy): Maybe[DSLCostPolicy]
+ }
- true
+ /**
+ * Find the cost policy of the resource named in this resource event.
+ *
+ * We do not expect cost policies for resources to change, because they are supposed
+ * to be one of their constant characteristics. That is why do not issue a time-dependent
+ * query here for the event's current policy.
+ *
+ * Should the need arises to change the cost policy for a resource, this is a good enough
+ * reason to consider creating another type of resource.
+ */
+ def findCostPolicy(resourcesMap: DSLResourcesMap): Option[DSLCostPolicy] = {
+ resourcesMap.findResource(this.safeResource).map(_.costPolicy)
}
}
object ResourceEvent {
+ type Details = Map[String, String]
+
+ type ResourceType = String
+ type ResourceIdType = String
+ type FullResourceType = (ResourceType, ResourceIdType)
+
def fromJson(json: String): ResourceEvent = {
- implicit val formats = JsonHelpers.DefaultJsonFormats
- val jsonAST = parseJson(json)
- Extraction.extract[ResourceEvent](jsonAST)
+ JsonHelpers.jsonToObject[ResourceEvent](json)
}
def fromJValue(jsonAST: JsonAST.JValue): ResourceEvent = {
- implicit val formats = JsonHelpers.DefaultJsonFormats
- Extraction.extract(jsonAST)
+ JsonHelpers.jValueToObject[ResourceEvent](jsonAST)
}
def fromBytes(bytes: Array[Byte]): ResourceEvent = {
- fromJson(new String(bytes, "UTF-8"))
+ JsonHelpers.jsonBytesToObject[ResourceEvent](bytes)
}
def fromXml(xml: String): ResourceEvent = {
fromJValue(Xml.toJson(scala.xml.XML.loadString(xml)))
}
+
+ object JsonNames {
+ final val _id = "_id"
+ final val id = "id"
+ final val userId = "userId"
+ //final val timestamp = "timestamp" // TODO: deprecate in favor of "occurredMillis"
+ final val occurredMillis = "occurredMillis"
+ final val receivedMillis = "receivedMillis"
+ final val clientId = "clientId"
+ final val resource = "resource"
+ final val resourceId = "resourceId"
+ final val eventVersion = "eventVersion"
+ final val value = "value"
+ final val details = "details"
+
+ // ResourceType: VMTime
+ final val vmId = "vmId"
+ final val action = "action" // "on", "off"
+ }
}
\ No newline at end of file