Repository: incubator-s2graph
Updated Branches:
  refs/heads/master ef0b6e51a -> 19254301d (forced update)


s2http initial commit


Project: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/commit/a16ecb08
Tree: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/tree/a16ecb08
Diff: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/diff/a16ecb08

Branch: refs/heads/master
Commit: a16ecb08819ea01f4498dfdd371717de56387a72
Parents: 66b656f
Author: daewon <dae...@apache.org>
Authored: Mon Nov 26 17:11:36 2018 +0900
Committer: daewon <dae...@apache.org>
Committed: Mon Nov 26 17:32:33 2018 +0900

----------------------------------------------------------------------
 build.sbt                                       |   8 +-
 s2core/src/main/resources/reference.conf        |  10 +-
 s2http/README.md                                |  47 +++++++
 s2http/build.sbt                                |  41 +++++++
 s2http/src/main/resources/application.conf      |  27 +++++
 s2http/src/main/resources/log4j.properties      |  26 ++++
 .../apache/s2graph/http/S2GraphAdminRoute.scala | 121 +++++++++++++++++++
 .../s2graph/http/S2GraphTraversalRoute.scala    |  65 ++++++++++
 .../scala/org/apache/s2graph/http/Server.scala  |  70 +++++++++++
 .../apache/s2graph/http/AdminRouteSpec.scala    |  61 ++++++++++
 10 files changed, 467 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/build.sbt
----------------------------------------------------------------------
diff --git a/build.sbt b/build.sbt
index 03e9bd0..c4d2c35 100755
--- a/build.sbt
+++ b/build.sbt
@@ -46,7 +46,7 @@ lazy val s2rest_play = 
project.enablePlugins(PlayScala).disablePlugins(PlayLogba
   .settings(dockerSettings: _*)
   .enablePlugins(sbtdocker.DockerPlugin, JavaAppPackaging)
 
-lazy val s2rest_netty = project
+lazy val s2http = project
   .dependsOn(s2core)
   .settings(commonSettings: _*)
   .settings(dockerSettings: _*)
@@ -78,8 +78,8 @@ lazy val s2graph_gremlin = project.dependsOn(s2core)
   .settings(commonSettings: _*)
 
 lazy val root = (project in file("."))
-  .aggregate(s2core, s2rest_play, s2jobs)
-  .dependsOn(s2rest_play, s2rest_netty, s2jobs, s2counter_loader, s2graphql) 
// this enables packaging on the root project
+  .aggregate(s2core, s2rest_play, s2jobs, s2http)
+  .dependsOn(s2rest_play, s2http, s2jobs, s2counter_loader, s2graphql) // this 
enables packaging on the root project
   .settings(commonSettings: _*)
 
 lazy val dockerSettings = Seq(
@@ -124,7 +124,7 @@ Packager.defaultSettings
 
 publishSigned := {
   (publishSigned in s2rest_play).value
-  (publishSigned in s2rest_netty).value
+  (publishSigned in s2http).value
   (publishSigned in s2core).value
   (publishSigned in spark).value
   (publishSigned in s2jobs).value

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2core/src/main/resources/reference.conf
----------------------------------------------------------------------
diff --git a/s2core/src/main/resources/reference.conf 
b/s2core/src/main/resources/reference.conf
index 73c949a..e0681dc 100644
--- a/s2core/src/main/resources/reference.conf
+++ b/s2core/src/main/resources/reference.conf
@@ -53,9 +53,9 @@ db.default.url = "jdbc:h2:file:./var/metastore;MODE=MYSQL"
 db.default.user = "sa"
 db.default.password = "sa"
 
-
-akka {
-  loggers = ["akka.event.slf4j.Slf4jLogger"]
-  loglevel = "DEBUG"
-}
+//
+//akka {
+//  loggers = ["akka.event.slf4j.Slf4jLogger"]
+//  loglevel = "DEBUG"
+//}
 

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/README.md
----------------------------------------------------------------------
diff --git a/s2http/README.md b/s2http/README.md
new file mode 100644
index 0000000..18f43a2
--- /dev/null
+++ b/s2http/README.md
@@ -0,0 +1,47 @@
+<!---
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+--->
+# S2Graph HTTP Layer
+
+## Development Setup
+
+### Run Server 
+Let's run http server.
+
+```bash
+sbt 'project s2http' '~reStart'
+```
+
+When the server is running, connect to `http://localhost:8000`. If it works 
normally, you can see the following screen.
+
+```json
+{
+  "port": 8000,
+  "started_at": 1543218853354
+}
+```
+
+### API testing
+```test
+sbt 'project s2http' "test-only *s2graph.http*"
+```
+
+### API List
+  - link to S2GraphDocument

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/build.sbt
----------------------------------------------------------------------
diff --git a/s2http/build.sbt b/s2http/build.sbt
new file mode 100644
index 0000000..944493c
--- /dev/null
+++ b/s2http/build.sbt
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+lazy val akkaHttpVersion = "10.1.5"
+lazy val akkaVersion = "2.5.18"
+
+name := "s2http"
+
+version := "0.1"
+
+description := "s2graph http server"
+
+scalacOptions ++= Seq("-deprecation", "-feature")
+
+libraryDependencies ++= Seq(
+  "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
+  "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
+  "com.typesafe.akka" %% "akka-stream" % akkaVersion,
+
+  "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
+  "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test,
+
+  "org.scalatest" %% "scalatest" % "3.0.5" % Test
+)
+
+Revolver.settings

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/main/resources/application.conf
----------------------------------------------------------------------
diff --git a/s2http/src/main/resources/application.conf 
b/s2http/src/main/resources/application.conf
new file mode 100644
index 0000000..7019e02
--- /dev/null
+++ b/s2http/src/main/resources/application.conf
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+//db.default.url="jdbc:h2:file:./var/metastore;MODE=MYSQL",
+//db.default.password = sa
+//db.default.user = sa
+//s2graph.storage.backend = rocks
+//rocks.storage.file.path = rocks_db
+//rocks.storage.mode = production
+//rocks.storage.ttl = -1
+//rocks.storage.read.only = false

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/s2http/src/main/resources/log4j.properties 
b/s2http/src/main/resources/log4j.properties
new file mode 100644
index 0000000..2070d82
--- /dev/null
+++ b/s2http/src/main/resources/log4j.properties
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/main/scala/org/apache/s2graph/http/S2GraphAdminRoute.scala
----------------------------------------------------------------------
diff --git 
a/s2http/src/main/scala/org/apache/s2graph/http/S2GraphAdminRoute.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphAdminRoute.scala
new file mode 100644
index 0000000..c2b832e
--- /dev/null
+++ b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphAdminRoute.scala
@@ -0,0 +1,121 @@
+package org.apache.s2graph.http
+
+import akka.http.scaladsl.model._
+import org.apache.s2graph.core.rest.RequestParser
+import org.apache.s2graph.core.{Management, S2Graph}
+
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.server.Directives._
+
+import org.slf4j.LoggerFactory
+import play.api.libs.json._
+
+import scala.util.Try
+
+object S2GraphAdminRoute {
+
+  import scala.util._
+
+  trait AdminMessageFormatter[T] {
+    def toJson(msg: T): JsValue
+  }
+
+  import scala.language.reflectiveCalls
+
+  type ToPlayJson = {def toJson: JsValue}
+
+  object AdminMessageFormatter {
+
+    implicit def toPlayJson[A <: ToPlayJson] = new AdminMessageFormatter[A] {
+      def toJson(js: A) = js.toJson
+    }
+
+    implicit def fromPlayJson[T <: JsValue] = new AdminMessageFormatter[T] {
+      def toJson(js: T) = js
+    }
+  }
+
+  def toHttpEntity[A: AdminMessageFormatter](opt: Option[A], status: 
StatusCode = StatusCodes.OK, message: String = ""): HttpResponse = {
+    val ev = implicitly[AdminMessageFormatter[A]]
+    val res = opt.map(ev.toJson).getOrElse(Json.obj("message" -> message))
+
+    HttpResponse(
+      status = status,
+      entity = HttpEntity(ContentTypes.`application/json`, res.toString)
+    )
+  }
+
+  def toHttpEntity[A: AdminMessageFormatter](opt: Try[A]): HttpResponse = {
+    val ev = implicitly[AdminMessageFormatter[A]]
+    val (status, res) = opt match {
+      case Success(m) => StatusCodes.Created -> Json.obj("status" -> "ok", 
"message" -> ev.toJson(m))
+      case Failure(e) => StatusCodes.OK -> Json.obj("status" -> "failure", 
"message" -> e.toString)
+    }
+
+    toHttpEntity(Option(res), status = status)
+  }
+}
+
+trait S2GraphAdminRoute {
+
+  import S2GraphAdminRoute._
+
+  val s2graph: S2Graph
+  val logger = LoggerFactory.getLogger(this.getClass)
+
+  lazy val management: Management = s2graph.management
+  lazy val requestParser: RequestParser = new RequestParser(s2graph)
+
+  // routes impl
+  lazy val getLabel = path("getLabel" / Segment) { labelName =>
+    val labelOpt = Management.findLabel(labelName)
+
+    complete(toHttpEntity(labelOpt, message = s"Label not found: 
${labelName}"))
+  }
+
+  lazy val getService = path("getService" / Segment) { serviceName =>
+    val serviceOpt = Management.findService(serviceName)
+
+    complete(toHttpEntity(serviceOpt, message = s"Service not found: 
${serviceName}"))
+  }
+
+  lazy val createService = path("createService") {
+    entity(as[String]) { body =>
+      val params = Json.parse(body)
+
+      val parseTry = Try(requestParser.toServiceElements(params))
+      val serviceTry = for {
+        (serviceName, cluster, tableName, preSplitSize, ttl, 
compressionAlgorithm) <- parseTry
+        service <- management.createService(serviceName, cluster, tableName, 
preSplitSize, ttl, compressionAlgorithm)
+      } yield service
+
+      complete(toHttpEntity(serviceTry))
+    }
+  }
+
+  lazy val createLabel = path("createLabel") {
+    entity(as[String]) { body =>
+      val params = Json.parse(body)
+
+      val labelTry = for {
+        label <- requestParser.toLabelElements(params)
+      } yield label
+
+      complete(toHttpEntity(labelTry))
+    }
+  }
+
+  // expose routes
+  lazy val adminRoute: Route =
+    get {
+      concat(
+        getService,
+        getLabel
+      )
+    } ~ post {
+      concat(
+        createService,
+        createLabel
+      )
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/main/scala/org/apache/s2graph/http/S2GraphTraversalRoute.scala
----------------------------------------------------------------------
diff --git 
a/s2http/src/main/scala/org/apache/s2graph/http/S2GraphTraversalRoute.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphTraversalRoute.scala
new file mode 100644
index 0000000..1814436
--- /dev/null
+++ b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphTraversalRoute.scala
@@ -0,0 +1,65 @@
+package org.apache.s2graph.http
+
+import org.apache.s2graph.core.S2Graph
+import org.apache.s2graph.core.rest.RestHandler.CanLookup
+import org.slf4j.LoggerFactory
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.model.headers.RawHeader
+import akka.http.scaladsl.model.{HttpHeader, StatusCodes}
+import org.apache.s2graph.core.GraphExceptions.{BadQueryException, 
JsonParseException}
+import org.apache.s2graph.core.rest.RestHandler
+
+
+object S2GraphTraversalRoute {
+
+  import scala.collection._
+
+  implicit val akkHttpHeaderLookup = new CanLookup[immutable.Seq[HttpHeader]] {
+    override def lookup(m: immutable.Seq[HttpHeader], key: String): 
Option[String] = m.find(_.name() == key).map(_.value())
+  }
+}
+
+trait S2GraphTraversalRoute {
+
+  import S2GraphTraversalRoute._
+
+  val s2graph: S2Graph
+  val logger = LoggerFactory.getLogger(this.getClass)
+
+  implicit lazy val ec = s2graph.ec
+  lazy val restHandler = new RestHandler(s2graph)
+
+  // The `/graphs/*` APIs are implemented to be branched from the existing 
restHandler.doPost.
+  // Implement it first by delegating that function.
+  lazy val delegated: Route = {
+    entity(as[String]) { body =>
+      logger.info(body)
+
+      extractRequest { request =>
+        val result = restHandler.doPost(request.uri.toRelative.toString(), 
body, request.headers)
+        val responseHeaders = result.headers.toList.map { case (k, v) => 
RawHeader(k, v) }
+
+        val f = result.body.map(StatusCodes.OK -> _.toString).recover {
+          case BadQueryException(msg, _) => StatusCodes.BadRequest -> msg
+          case JsonParseException(msg) => StatusCodes.BadRequest -> msg
+          case e: Exception => StatusCodes.InternalServerError -> e.toString
+        }
+
+        respondWithHeaders(responseHeaders) {
+          complete(f)
+        }
+      }
+    }
+  }
+
+  // expose routes
+  lazy val traversalRoute: Route =
+    post {
+      concat(
+        delegated // getEdges, experiments, etc.
+      )
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
----------------------------------------------------------------------
diff --git a/s2http/src/main/scala/org/apache/s2graph/http/Server.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
new file mode 100644
index 0000000..82d2343
--- /dev/null
+++ b/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.s2graph.http
+
+import scala.language.postfixOps
+import scala.concurrent.{Await, ExecutionContext, Future}
+import scala.concurrent.duration.Duration
+import scala.util.{Failure, Success}
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpResponse, 
StatusCodes}
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.server.Directives._
+import akka.stream.ActorMaterializer
+import com.typesafe.config.ConfigFactory
+import org.apache.s2graph.core.S2Graph
+import org.slf4j.LoggerFactory
+
+object Server extends App
+  with S2GraphTraversalRoute
+  with S2GraphAdminRoute {
+
+  implicit val system: ActorSystem = ActorSystem("S2GraphHttpServer")
+  implicit val materializer: ActorMaterializer = ActorMaterializer()
+  implicit val executionContext: ExecutionContext = system.dispatcher
+
+  val config = ConfigFactory.load()
+
+  override val s2graph = new S2Graph(config)
+  override val logger = LoggerFactory.getLogger(this.getClass)
+
+  val port = sys.props.get("http.port").fold(8000)(_.toInt)
+  val serverStatus = s""" { "port": ${port}, "started_at": 
${System.currentTimeMillis()} }"""
+
+  val health = HttpResponse(status = StatusCodes.OK, entity = 
HttpEntity(ContentTypes.`application/json`, serverStatus))
+
+  // Allows you to determine routes to expose according to external settings.
+  lazy val routes: Route = concat(
+    pathPrefix("graphs")(traversalRoute),
+    pathPrefix("admin")(adminRoute),
+    get(complete(health))
+  )
+
+  val binding: Future[Http.ServerBinding] = Http().bindAndHandle(routes, 
"localhost", port)
+  binding.onComplete {
+    case Success(bound) => logger.info(s"Server online at 
http://${bound.localAddress.getHostString}:${bound.localAddress.getPort}/";)
+    case Failure(e) =>
+      logger.error(s"Server could not start!", e)
+      system.terminate()
+  }
+
+  Await.result(system.whenTerminated, Duration.Inf)
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/a16ecb08/s2http/src/test/scala/org/apache/s2graph/http/AdminRouteSpec.scala
----------------------------------------------------------------------
diff --git a/s2http/src/test/scala/org/apache/s2graph/http/AdminRouteSpec.scala 
b/s2http/src/test/scala/org/apache/s2graph/http/AdminRouteSpec.scala
new file mode 100644
index 0000000..7e5d794
--- /dev/null
+++ b/s2http/src/test/scala/org/apache/s2graph/http/AdminRouteSpec.scala
@@ -0,0 +1,61 @@
+package org.apache.s2graph.http
+
+import akka.http.scaladsl.marshalling.Marshal
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.testkit.ScalatestRouteTest
+import com.typesafe.config.ConfigFactory
+import org.apache.s2graph.core.S2Graph
+import org.scalatest.concurrent.ScalaFutures
+import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpec}
+import org.slf4j.LoggerFactory
+import play.api.libs.json.{JsString, Json}
+
+class AdminRoutesSpec extends WordSpec with Matchers with ScalaFutures with 
ScalatestRouteTest with S2GraphAdminRoute with BeforeAndAfterAll {
+  val config = ConfigFactory.load()
+  val s2graph = new S2Graph(config)
+  override val logger = LoggerFactory.getLogger(this.getClass)
+
+  override def afterAll(): Unit = {
+    s2graph.shutdown()
+  }
+
+  import akka.http.scaladsl.server.Directives._
+
+  lazy val routes = adminRoute
+
+  "AdminRoute" should {
+    "be able to create service (POST /createService)" in {
+      val serviceParam = Json.obj(
+        "serviceName" -> "kakaoFavorites",
+        "compressionAlgorithm" -> "gz"
+      )
+
+      val serviceEntity = 
Marshal(serviceParam.toString).to[MessageEntity].futureValue
+      val request = Post("/createService").withEntity(serviceEntity)
+
+      request ~> routes ~> check {
+        status should ===(StatusCodes.Created)
+        contentType should ===(ContentTypes.`application/json`)
+
+        val response = Json.parse(entityAs[String])
+
+        (response \\ "name").head should ===(JsString("kakaoFavorites"))
+        (response \\ "status").head should ===(JsString("ok"))
+      }
+    }
+
+    "return service if present (GET /getService/{serviceName})" in {
+      val request = HttpRequest(uri = "/getService/kakaoFavorites")
+
+      request ~> routes ~> check {
+        status should ===(StatusCodes.OK)
+        contentType should ===(ContentTypes.`application/json`)
+
+        val response = Json.parse(entityAs[String])
+
+        (response \\ "name").head should ===(JsString("kakaoFavorites"))
+      }
+    }
+  }
+}
+

Reply via email to