Skip to content

Commit e7eff6a

Browse files
Merge pull request #14 from delphi-hub/develop
Fully functional API implementation
2 parents 82e2038 + d679ee9 commit e7eff6a

File tree

8 files changed

+244
-17
lines changed

8 files changed

+244
-17
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
*.class
22
*.log
3+
.idea
4+
project/target/
5+
target/

OpenAPISpecification.yaml

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ info:
33
description: "This is a sample client side for Delphi Instance Registry."
44
version: "1.0.0"
55
title: "Delphi Instance Registry"
6-
host: "localhost:8085"
6+
host: "localhost:8087"
77
consumes:
8-
- "application/json"
8+
- "application/json"
99
produces:
10-
- "application/json"
10+
- "application/json"
1111
basePath: "/"
1212
tags:
1313
- name: "instance"
@@ -32,7 +32,10 @@ paths:
3232
$ref: "#/definitions/Instance"
3333
responses:
3434
200:
35-
description: "Registeration Successful"
35+
description: "Successfully Registered"
36+
schema:
37+
type: "integer"
38+
format: "int64"
3639
405:
3740
description: "Invalid input"
3841
/deregister:
@@ -48,7 +51,7 @@ paths:
4851
description: "Details of Instance to be deleted"
4952
required: true
5053
schema:
51-
$ref: "#/definitions/InstanceID"
54+
$ref: "#/definitions/Instance"
5255
responses:
5356
200:
5457
description: "Sucessfully Deregistered"
@@ -58,13 +61,13 @@ paths:
5861
description: "Instance not found"
5962
405:
6063
description: "Validation exception"
61-
64+
6265
/matchingInstance:
6366
get:
6467
tags:
6568
- "instance"
6669
summary: " Get Matching Instances"
67-
operationId: "matchInstance"
70+
operationId: "getMatchingInstance"
6871
parameters:
6972
- name: "ComponentType"
7073
in: "query"
@@ -87,7 +90,7 @@ paths:
8790
- "instance"
8891
summary: "Fetch Instances"
8992
description: "Fetch Specific Instance"
90-
operationId: "fetchInstance"
93+
operationId: "fetchInstanceOfType"
9194
parameters:
9295
- name: "ComponentType"
9396
in: "query"
@@ -113,8 +116,8 @@ paths:
113116
- "instance"
114117
summary: "Find number of running instances"
115118
description: "How many instances per type are running"
116-
operationId: "getInstanceNumber"
117-
119+
operationId: "numberOfInstances"
120+
118121
parameters:
119122
- name: "ComponentType"
120123
in: "query"
@@ -141,7 +144,7 @@ paths:
141144
- "instance"
142145
summary: "Find the matching instance"
143146
description: "Match the instance"
144-
operationId: "getMatchingInstance"
147+
operationId: "matchInstance"
145148
parameters:
146149
- name: "MatchingSuccessful"
147150
in: "query"
@@ -174,6 +177,7 @@ definitions:
174177
type: "integer"
175178
format: "int64"
176179
name:
180+
177181
type: "string"
178182
ComponentType:
179183
type: "string"
@@ -183,9 +187,4 @@ definitions:
183187
- "WebApi"
184188
- "WebApp"
185189
- "DelphiManagement"
186-
InstanceID:
187-
type: "object"
188-
properties:
189-
ID:
190-
type: "integer"
191-
format: "int64"
190+
- "ElasticSearch"

build.sbt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name := "delphi-registry"
2+
3+
version := "1.0.0-SNAPSHOT"
4+
5+
scalaVersion := "2.12.4"
6+
7+
8+
val akkaVersion = "2.5.14"
9+
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.0.11"
10+
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % akkaVersion
11+
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.1"
12+
13+
libraryDependencies += "io.spray" %% "spray-json" % "1.3.3"
14+
15+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test"

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version = 1.2.1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package de.upb.cs.swt.delphi.instanceregistry
2+
3+
import akka.actor.{ActorSystem, ExtendedActorSystem}
4+
import akka.event.{BusLogging, LoggingAdapter}
5+
6+
trait AppLogging {
7+
def log(implicit system: ActorSystem): LoggingAdapter = new BusLogging(system.eventStream, this.getClass.getName, this.getClass, system.asInstanceOf[ExtendedActorSystem].logFilter)
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package de.upb.cs.swt.delphi.instanceregistry
2+
3+
class Configuration( //Server configuration
4+
val bindHost: String = "0.0.0.0",
5+
val bindPort: Int = 8087,
6+
)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package de.upb.cs.swt.delphi.instanceregistry
2+
3+
import java.util.concurrent.TimeUnit
4+
5+
import akka.actor.ActorSystem
6+
import akka.http.scaladsl.server
7+
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
8+
import akka.http.scaladsl.server.HttpApp
9+
import akka.http.scaladsl.unmarshalling.Unmarshal
10+
import akka.stream.ActorMaterializer
11+
import akka.util.Timeout
12+
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.InstanceEnums.ComponentType
13+
import io.swagger.client.model.{Instance, JsonSupport}
14+
15+
import scala.collection.mutable
16+
import scala.concurrent.{Await, ExecutionContext}
17+
import scala.concurrent.duration.Duration
18+
19+
20+
/**
21+
* Web server configuration for Instance Registry API.
22+
*/
23+
object Server extends HttpApp with JsonSupport with AppLogging {
24+
25+
//Default ES instance for testing
26+
private val instances = mutable.HashSet (Instance(Some(0), "elasticsearch://localhost", 9200, "Default ElasticSearch Instance", ComponentType.ElasticSearch))
27+
28+
implicit val system : ActorSystem = ActorSystem("delphi-registry")
29+
implicit val materializer : ActorMaterializer = ActorMaterializer()
30+
implicit val ec : ExecutionContext = system.dispatcher
31+
implicit val timeout : Timeout = Timeout(5, TimeUnit.SECONDS)
32+
33+
override def routes : server.Route =
34+
path("register") {entity(as[String]) { jsonString => addInstance(jsonString) }} ~
35+
path("deregister") { deleteInstance() } ~
36+
path("instances" ) { fetchInstancesOfType() } ~
37+
path("numberOfInstances" ) { numberOfInstances() } ~
38+
path("matchingInstance" ) { getMatchingInstance()} ~
39+
path("matchingResult" ) {matchInstance()}
40+
41+
42+
def addInstance(InstanceString: String) : server.Route = {
43+
post
44+
{
45+
log.debug(s"POST /register has been called, parameter is: $InstanceString")
46+
Await.result(Unmarshal(InstanceString).to[Instance] map {paramInstance =>
47+
val name = paramInstance.name
48+
val newID : Long = {
49+
if(instances.isEmpty){
50+
0L
51+
}
52+
else{
53+
(instances map( instance => instance.iD.get) max) + 1L
54+
}
55+
}
56+
57+
val instanceToRegister = Instance(iD = Some(newID), host = paramInstance.host, portnumber = paramInstance.portnumber, name = paramInstance.name, componentType = paramInstance.componentType)
58+
59+
instances += instanceToRegister
60+
log.info(s"Instance with name $name registered, ID $newID assigned.")
61+
62+
complete {newID.toString()}
63+
} recover {case ex =>
64+
log.warning(s"Failed to read registering instance, exception: $ex")
65+
complete(HttpResponse(StatusCodes.InternalServerError, entity = "Failed to unmarshal parameter."))
66+
}, Duration.Inf)
67+
}
68+
}
69+
70+
def deleteInstance() : server.Route = parameters('Id.as[Long]){ Id =>
71+
post {
72+
log.debug(s"POST /deregister?Id=$Id has been called")
73+
74+
val instanceToRemove = instances find(instance => instance.iD.get == Id)
75+
76+
if(instanceToRemove.isEmpty){
77+
log.warning(s"Cannot remove instance with id $Id, that id is not present on the server")
78+
complete{HttpResponse(StatusCodes.NotFound, entity = s"Id $Id not present on the server")}
79+
}
80+
else{
81+
instances remove instanceToRemove.get
82+
log.info(s"Successfully removed instance with id $Id")
83+
complete {s"Successfully removed instance with id $Id"}
84+
}
85+
}
86+
}
87+
def fetchInstancesOfType () : server.Route = parameters('ComponentType.as[String]) { compTypeString =>
88+
get {
89+
log.debug(s"GET /instances?ComponentType=$compTypeString has been called")
90+
val compType : ComponentType = ComponentType.values.find(v => v.toString == compTypeString).orNull
91+
val matchingInstancesList = List() ++ instances filter {instance => instance.componentType == compType}
92+
93+
complete {matchingInstancesList}
94+
}
95+
}
96+
97+
def numberOfInstances() : server.Route = parameters('ComponentType.as[String]) { compTypeString =>
98+
get {
99+
log.debug(s"GET /numberOfInstances?ComponentType=$compTypeString has been called")
100+
val compType : ComponentType = ComponentType.values.find(v => v.toString == compTypeString).orNull
101+
val count : Int = instances count {instance => instance.componentType == compType}
102+
complete{count.toString()}
103+
}
104+
}
105+
106+
def getMatchingInstance() : server.Route = parameters('ComponentType.as[String]){ compTypeString =>
107+
get{
108+
log.debug(s"GET /matchingInstance?ComponentType=$compTypeString has been called")
109+
val compType : ComponentType = ComponentType.values.find(v => v.toString == compTypeString).orNull
110+
log.info(s"Looking for instance of type $compType ...")
111+
val matchingInstances = instances filter {instance => instance.componentType == compType}
112+
if(matchingInstances.isEmpty){
113+
log.warning(s"Could not find matching instance for type $compType .")
114+
complete(HttpResponse(StatusCodes.NotFound, entity = s"Could not find matching instance for type $compType"))
115+
}
116+
else {
117+
val matchedInstance = matchingInstances.iterator.next()
118+
log.info(s"Matched to $matchedInstance.")
119+
complete(matchedInstance)
120+
}
121+
122+
}
123+
}
124+
125+
def matchInstance() : server.Route = parameters('Id.as[Long], 'MatchingSuccessful.as[Boolean]){ (Id, MatchingResult) =>
126+
post {
127+
//TODO: Need to keep track of matching, maybe remove instances if not reachable!
128+
log.debug(s"POST /matchingResult?Id=$Id&MatchingSuccessful=$MatchingResult has been called")
129+
if(MatchingResult){
130+
log.info(s"Instance with Id $Id was successfully matched.")
131+
}
132+
else{
133+
log.warning(s"A client was not able to reach matched instance with Id $Id !")
134+
}
135+
complete {s"Matching result $MatchingResult processed."}
136+
}
137+
}
138+
139+
def main(args: Array[String]): Unit = {
140+
val configuration = new Configuration()
141+
Server.startServer(configuration.bindHost, configuration.bindPort)
142+
system.terminate()
143+
}
144+
145+
146+
}
147+
148+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model
2+
3+
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
4+
import spray.json.{DefaultJsonProtocol, JsonFormat,JsString, JsValue}
5+
6+
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
7+
8+
implicit val componentTypeFormat = new JsonFormat[InstanceEnums.ComponentType] {
9+
10+
def write(compType : InstanceEnums.ComponentType) = JsString(compType.toString)
11+
12+
def read(value: JsValue) : InstanceEnums.ComponentType = value match {
13+
case JsString(s) => s match {
14+
case "Crawler" => InstanceEnums.ComponentType.Crawler
15+
case "WebApi" => InstanceEnums.ComponentType.WebApi
16+
case "WebApp" => InstanceEnums.ComponentType.WebApp
17+
case "DelphiManagement" => InstanceEnums.ComponentType.DelphiManagement
18+
case "ElasticSearch" => InstanceEnums.ComponentType.ElasticSearch
19+
case x => throw new RuntimeException(s"Unexpected string value $x for component type.")
20+
}
21+
case y => throw new RuntimeException(s"Unexpected type $y while deserializing component type.")
22+
}
23+
}
24+
implicit val instanceFormat = jsonFormat5(Instance)
25+
}
26+
27+
final case class Instance (
28+
iD: Option[Long],
29+
host: String,
30+
portnumber: Long,
31+
name: String,
32+
/* Component Type */
33+
componentType: InstanceEnums.ComponentType
34+
)
35+
36+
object InstanceEnums {
37+
38+
type ComponentType = ComponentType.Value
39+
object ComponentType extends Enumeration {
40+
val Crawler : Value = Value("Crawler")
41+
val WebApi : Value = Value("WebApi")
42+
val WebApp : Value = Value("WebApp")
43+
val DelphiManagement : Value = Value("DelphiManagement")
44+
val ElasticSearch : Value = Value("ElasticSearch")
45+
}
46+
47+
}

0 commit comments

Comments
 (0)