From 691ba6c4b6b3294f91f6e8cd4c8ccc18bf762510 Mon Sep 17 00:00:00 2001 From: Christos KK Loverdos Date: Fri, 8 Jun 2012 17:49:24 +0300 Subject: [PATCH] Add two more REST internal calls - Get rc event by id. - Get im event by id. Calls are protected by the admin.cookie property --- .../aquarium/actor/service/rest/RESTActor.scala | 95 ++++++++++++++------ .../aquarium/actor/service/rest/RESTPaths.scala | 9 +- .../aquarium/actor/service/user/UserActor.scala | 15 ++-- .../computation/UserStateComputations.scala | 4 +- .../computation/state/parts/IMStateSnapshot.scala | 3 +- .../logic/accounting/dsl/DSLResourcesMap.scala | 4 +- .../aquarium/store/mongodb/MongoDBStore.scala | 2 - 7 files changed, 92 insertions(+), 40 deletions(-) diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTActor.scala index 9f1b317..ec6dffd 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTActor.scala @@ -52,6 +52,7 @@ import gr.grnet.aquarium.{ResourceLocator, Aquarium} import com.ckkloverdos.resource.StreamResource import com.ckkloverdos.maybe.{Failed, NoVal, Just} import java.net.InetAddress +import gr.grnet.aquarium.event.model.{ExternalEventModel, EventModel} /** * Spray-based REST service. This is the outer-world's interface to Aquarium functionality. @@ -63,9 +64,13 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { self.id = _id + final val TEXT_PLAIN = "text/plain" + final val APPLICATION_JSON = "application/json" + + private[this] def aquarium = Aquarium.Instance - private def stringResponse(status: Int, stringBody: String, contentType: String = "application/json"): HttpResponse = { + private def stringResponse(status: Int, stringBody: String, contentType: String): HttpResponse = { HttpResponse( status, HttpHeader("Content-type", "%s;charset=utf-8".format(contentType)) :: Nil, @@ -86,13 +91,31 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { res match { case Failed(e) ⇒ logger.error("While serving %s".format(uri), e) - responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), "text/plain")) + responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), TEXT_PLAIN)) case _ ⇒ } } + private def eventInfoResponse[E <: ExternalEventModel]( + uri: String, + responder: RequestResponder, + getter: String ⇒ Option[E], + eventID: String + ): Unit = { + + val toSend = getter.apply(eventID) match { + case Some(event) ⇒ + (200, event.toJsonString, APPLICATION_JSON) + + case None ⇒ + (404, "Event not found", TEXT_PLAIN) + } + + responder.complete(stringResponse(toSend._1, toSend._2, toSend._3)) + } + def withAdminCookie( uri: String, responder: RequestResponder, @@ -108,24 +131,24 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { catch { case e: Throwable ⇒ logger.error("While serving %s".format(uri), e) - responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), "text/plain")) + responder.complete(stringResponse(501, "Internal Server Error: %s".format(shortInfoOf(e)), TEXT_PLAIN)) } case Some(cookieHeader) ⇒ logger.warn("Admin request %s with bad cookie '%s' from %s".format(uri, cookieHeader.value, remoteAddress)) - responder.complete(stringResponse(401, "Unauthorized!", "text/plain")) + responder.complete(stringResponse(401, "Unauthorized!", TEXT_PLAIN)) case None ⇒ logger.warn("Admin request %s with no cookie from %s".format(uri, remoteAddress)) - responder.complete(stringResponse(401, "Unauthorized!", "text/plain")) + responder.complete(stringResponse(401, "Unauthorized!", TEXT_PLAIN)) } case NoVal ⇒ - responder.complete(stringResponse(403, "Forbidden!", "text/plain")) + responder.complete(stringResponse(403, "Forbidden!", TEXT_PLAIN)) } } - private def stringResponse200(stringBody: String, contentType: String = "application/json"): HttpResponse = { + private def stringResponse200(stringBody: String, contentType: String): HttpResponse = { stringResponse(200, stringBody, contentType) } @@ -133,7 +156,7 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { case RequestContext(HttpRequest(GET, "/ping", _, _, _), _, responder) ⇒ val now = TimeHelpers.nowMillis() val nowFormatted = ISODateTimeFormat.dateTime().print(now) - responder.complete(stringResponse200("PONG\n%s\n%s".format(now, nowFormatted), "text/plain")) + responder.complete(stringResponse200("PONG\n%s\n%s".format(now, nowFormatted), TEXT_PLAIN)) case RequestContext(HttpRequest(GET, "/stats", _, _, _), _, responder) ⇒ { (serverActor ? GetStats).mapTo[Stats].onComplete { @@ -142,18 +165,23 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { case Right(stats) => responder.complete { stringResponse200( "Uptime : " + (stats.uptime / 1000.0) + " sec\n" + - "Requests dispatched : " + stats.requestsDispatched + '\n' + - "Requests timed out : " + stats.requestsTimedOut + '\n' + - "Requests open : " + stats.requestsOpen + '\n' + - "Open connections : " + stats.connectionsOpen + '\n' + "Requests dispatched : " + stats.requestsDispatched + '\n' + + "Requests timed out : " + stats.requestsTimedOut + '\n' + + "Requests open : " + stats.requestsOpen + '\n' + + "Open connections : " + stats.connectionsOpen + '\n', + TEXT_PLAIN ) } - case Left(ex) => responder.complete(stringResponse(500, "Couldn't get server stats due to " + ex, "text/plain")) + case Left(ex) => responder.complete(stringResponse(500, "Couldn't get server stats due to " + ex, TEXT_PLAIN)) } } } case RequestContext(HttpRequest(GET, uri, headers, body, protocol), remoteAddress, responder) ⇒ + def withAdminCookieHelper(f: RequestResponder ⇒ Unit): Unit = { + withAdminCookie(uri, responder, headers, remoteAddress)(f) + } + //+ Main business logic REST URIs are matched here val millis = TimeHelpers.nowMillis() uri match { @@ -166,32 +194,42 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { callRouter(GetUserStateRequest(userId, millis), responder) case AdminPingAll() ⇒ - withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒ + withAdminCookieHelper { responder ⇒ callRouter(PingAllRequest(), responder) } case ResourcesAquariumProperties() ⇒ - withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒ - resourceInfoResponse(uri, responder, ResourceLocator.Resources.AquariumPropertiesResource, "text/plain") + withAdminCookieHelper { responder ⇒ + resourceInfoResponse(uri, responder, ResourceLocator.Resources.AquariumPropertiesResource, TEXT_PLAIN) } case ResourcesLogbackXML() ⇒ - withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒ - resourceInfoResponse(uri, responder, ResourceLocator.Resources.LogbackXMLResource, "text/plain") + withAdminCookieHelper { responder ⇒ + resourceInfoResponse(uri, responder, ResourceLocator.Resources.LogbackXMLResource, TEXT_PLAIN) } case ResourcesPolicyYAML() ⇒ - withAdminCookie(uri, responder, headers, remoteAddress) { responder ⇒ - resourceInfoResponse(uri, responder, ResourceLocator.Resources.PolicyYAMLResource, "text/plain") + withAdminCookieHelper { responder ⇒ + resourceInfoResponse(uri, responder, ResourceLocator.Resources.PolicyYAMLResource, TEXT_PLAIN) + } + + case ResourceEventPath(id) ⇒ + withAdminCookieHelper { responder ⇒ + eventInfoResponse(uri, responder, aquarium.resourceEventStore.findResourceEventById, id) + } + + case IMEventPath(id) ⇒ + withAdminCookieHelper { responder ⇒ + eventInfoResponse(uri, responder, aquarium.imEventStore.findIMEventById, id) } case _ ⇒ - responder.complete(stringResponse(404, "Unknown resource!", "text/plain")) + responder.complete(stringResponse(404, "Unknown resource!", TEXT_PLAIN)) } //- Main business logic REST URIs are matched here case RequestContext(HttpRequest(_, _, _, _, _), _, responder) ⇒ - responder.complete(stringResponse(404, "Unknown resource!", "text/plain")) + responder.complete(stringResponse(404, "Unknown resource!", TEXT_PLAIN)) case Timeout(method, uri, _, _, _, complete) ⇒ complete { HttpResponse(status = 500).withBody("The " + method + " request to '" + uri + "' has timed out...") @@ -212,12 +250,12 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { // TODO: Will this ever happen?? logger.warn("Future did not complete for %s".format(message)) val statusCode = 500 - responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain")) + responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN)) case Some(Left(error)) ⇒ val statusCode = 500 logger.error("Error %s serving %s: %s".format(statusCode, message, error)) - responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain")) + responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN)) case Some(Right(actualResponse)) ⇒ actualResponse match { @@ -232,20 +270,21 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { message, actualResponse)) - responder.complete(stringResponse(statusCode, errorMessage, "text/plain")) + responder.complete(stringResponse(statusCode, errorMessage, TEXT_PLAIN)) case Right(response) ⇒ responder.complete( HttpResponse( routerResponse.suggestedHTTPStatus, body = routerResponse.responseToJsonString.getBytes("UTF-8"), - headers = HttpHeader("Content-type", "application/json;charset=utf-8") :: Nil)) + headers = HttpHeader("Content-type", APPLICATION_JSON+";charset=utf-8") :: + Nil)) } case _ ⇒ val statusCode = 500 logger.error("Error %s serving %s: Response is: %s".format(statusCode, message, actualResponse)) - responder.complete(stringResponse(statusCode, "Internal Server Error", "text/plain")) + responder.complete(stringResponse(statusCode, "Internal Server Error", TEXT_PLAIN)) } } } @@ -253,7 +292,7 @@ class RESTActor private(_id: String) extends RoleableActor with Loggable { ////////////// helpers ////////////// - val defaultHeaders = List(HttpHeader("Content-Type", "text/plain")) + final val defaultHeaders = List(HttpHeader("Content-Type", TEXT_PLAIN)) lazy val serverActor = Actor.registry.actorsFor("spray-can-server").head diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTPaths.scala b/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTPaths.scala index fad277b..fc796c8 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTPaths.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/rest/RESTPaths.scala @@ -51,16 +51,21 @@ object RESTPaths { final val ResourcesPolicyYAML = "/resources/policy\\.yaml".r final val RolesAgreementsMap = "/resources/roles-agreements\\.map".r + + final val ResourceEventPath = "/rcevent/([^/]+)/?".r + + final val IMEventPath = "/imevent/([^/]+)/?".r + /** * Use this URI path to query for the user balance. The parenthesized regular expression part * represents the user ID. */ - final val UserBalancePath = "/user/(.+)/balance/?".r + final val UserBalancePath = "/user/([^/]+)/balance/?".r /** * Use this URI path to query for the user state. */ - final val UserStatePath = "/user/(.+)/state/?".r + final val UserStatePath = "/user/([^/]+)/state/?".r /** * Use this administrative URI path to ping all services used by Aquarium. diff --git a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala index 95d04fe..7326267 100644 --- a/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala +++ b/src/main/scala/gr/grnet/aquarium/actor/service/user/UserActor.scala @@ -150,7 +150,8 @@ class UserActor extends ReflectiveRoleableActor { ) } - DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState) + DEBUG("Initial %s = %s", shortNameOfType[IMStateSnapshot], this._imState.toJsonString) + logSeparator() } /** @@ -211,7 +212,8 @@ class UserActor extends ReflectiveRoleableActor { loadUserStateAndUpdateRoleHistory() if(haveUserState) { - DEBUG("%s = %s", shortNameOfType[UserState], this._userState) + DEBUG("Initial %s = %s", shortNameOfType[UserState], this._userState.toJsonString) + logSeparator() } } @@ -278,7 +280,7 @@ class UserActor extends ReflectiveRoleableActor { } } - DEBUG("New %s = %s", shortNameOfType[IMStateSnapshot], this._imState) + DEBUG("Updated %s = %s", shortNameOfType[IMStateSnapshot], this._imState.toJsonString) logSeparator() } @@ -313,6 +315,8 @@ class UserActor extends ReflectiveRoleableActor { this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis DEBUG("Below threshold (%s ms). Not processing %s", this._timestampTheshold, rcEvent.toJsonString) + logSeparator() + return } @@ -333,7 +337,7 @@ class UserActor extends ReflectiveRoleableActor { val calculationReason = RealtimeBillingCalculation(None, now) val eventOccurredMillis = rcEvent.occurredMillis - DEBUG("Using %s", currentResourcesMap) +// DEBUG("Using %s", currentResourcesMap.toJsonString) this._userState = aquarium.userStateComputations.doMonthBillingUpTo( billingMonthInfo, @@ -346,7 +350,8 @@ class UserActor extends ReflectiveRoleableActor { this._latestResourceEventOccurredMillis = event.rcEvent.occurredMillis - DEBUG("New %s = %s", shortClassNameOf(this._userState), this._userState) + DEBUG("Updated %s = %s", shortClassNameOf(this._userState), this._userState.toJsonString) + logSeparator() } def onGetUserBalanceRequest(event: GetUserBalanceRequest): Unit = { diff --git a/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala index ca613d1..d9b85ab 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/UserStateComputations.scala @@ -150,7 +150,9 @@ final class UserStateComputations(_aquarium: => Aquarium) extends Loggable { // ZERO, we are OK! case 0 ⇒ // NOTE: Keep the caller's calculation reason - latestUserState.newWithChangeReason(calculationReason) + val result = latestUserState.newWithChangeReason(calculationReason) + clog.end() + result // We had more, so must recompute case n if n > 0 ⇒ diff --git a/src/main/scala/gr/grnet/aquarium/computation/state/parts/IMStateSnapshot.scala b/src/main/scala/gr/grnet/aquarium/computation/state/parts/IMStateSnapshot.scala index 1495857..b4d3193 100644 --- a/src/main/scala/gr/grnet/aquarium/computation/state/parts/IMStateSnapshot.scala +++ b/src/main/scala/gr/grnet/aquarium/computation/state/parts/IMStateSnapshot.scala @@ -41,6 +41,7 @@ import gr.grnet.aquarium.event.model.im.IMEventModel import gr.grnet.aquarium.util.shortClassNameOf import gr.grnet.aquarium.util.date.MutableDateCalc import gr.grnet.aquarium.computation.parts.RoleHistory +import gr.grnet.aquarium.util.json.JsonSupport /** * @@ -67,7 +68,7 @@ case class IMStateSnapshot( * This is the recorded role history */ roleHistory: RoleHistory -) { +) extends JsonSupport { /** * True iff the user has ever been activated even once. diff --git a/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLResourcesMap.scala b/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLResourcesMap.scala index 94de94c..00fe6ed 100644 --- a/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLResourcesMap.scala +++ b/src/main/scala/gr/grnet/aquarium/logic/accounting/dsl/DSLResourcesMap.scala @@ -35,6 +35,8 @@ package gr.grnet.aquarium.logic.accounting.dsl +import gr.grnet.aquarium.util.json.JsonSupport + /** * Enumerates known resources by name. @@ -45,7 +47,7 @@ package gr.grnet.aquarium.logic.accounting.dsl * @author Christos KK Loverdos */ -final class DSLResourcesMap(underlying: Map[String, DSLResource]) { +final case class DSLResourcesMap(underlying: Map[String, DSLResource]) extends JsonSupport { def this(list: List[DSLResource]) = this(Map(list.map(r ⇒ (r.name, r)): _*)) def map = underlying diff --git a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala index 04ecf59..458e5fe 100644 --- a/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala +++ b/src/main/scala/gr/grnet/aquarium/store/mongodb/MongoDBStore.scala @@ -236,8 +236,6 @@ class MongoDBStore( add(UserState.JsonNames.theFullBillingMonth_month, bmi.month). get() - logger.debug("findLatestUserStateForFullMonthBilling(%s, %s) query: %s".format(userID, bmi, query)) - // Descending order, so that the latest comes first val sorter = new BasicDBObject(UserState.JsonNames.occurredMillis, -1) -- 1.7.10.4