diff --git a/docs/load-test.md b/docs/load-test.md index cf456298..23ba6e70 100644 --- a/docs/load-test.md +++ b/docs/load-test.md @@ -4,9 +4,9 @@ Testing environment to be determined. ### Message publishing -In this scenario, large number of requests are sent to HTTP adapter service -every second. This test checks how much time HTTP adapter took to response to -each request. +In this scenario, large number of requests are sent to HTTP adapter service +every second. This test checks how much time HTTP adapter needs to respond +to each request. #### Results @@ -14,10 +14,10 @@ TBD ### Create and get client -In this scenario, large number of requests are sent to manager service to create -client, and than to retrieve its data. This test checks how much time manager -service took to response to each request. +In this scenario, large number of requests are sent to things service to create +things, and than to retrieve their data. This test checks how much time things +service needs to respond to each request. #### Results -TBD \ No newline at end of file +TBD diff --git a/load-test/.gitignore b/load-test/.gitignore index 680e4c47..92322c4e 100644 --- a/load-test/.gitignore +++ b/load-test/.gitignore @@ -1,8 +1,2 @@ +.idea/ target/ -.classpath -.cache-tests -.cache-main -.settings/ -.project -*.class -bin/ diff --git a/load-test/README.md b/load-test/README.md index 432b5d59..96ebd39b 100644 --- a/load-test/README.md +++ b/load-test/README.md @@ -1,10 +1,11 @@ # Load Test -This SBT project contains load tests written for mainflux platform. +This project contains platform's load tests. -## Setup +## Prerequisites -In order to run load tests you must have [openjdk8](http://openjdk.java.net/install/) and [sbt](https://www.scala-sbt.org/1.0/docs/Setup.html) installed on your machine. +To run the tests you must have [OpenJDK8](http://openjdk.java.net/install/) and +[SBT](https://www.scala-sbt.org/1.0/docs/Setup.html) installed on your machine. ## Configuration @@ -15,7 +16,7 @@ default values. | Variable | Description | Default | |----------|------------------------------------------|-----------------------| | users | Users service URL | http://localhost:8180 | -| clients | Clients service URL | http://localhost:8182 | +| things | Things service URL | http://localhost:8182 | | http | HTTP adapter service URL | http://localhost:8185 | | requests | Number of requests to be sent per second | 100 | @@ -23,8 +24,8 @@ default values. This project contains two simulations: -- `PublishSimulation` -- `CreateAndRetrieveClientSimulation` +- `CreateAndRetrieveThings` +- `PublishMessages` To run all tests you will have to run following commands: @@ -33,18 +34,20 @@ cd /load-test sbt gatling:test ``` -### Publish Simulation +### Things creation and retrieval -`PublishSimulation` contains load tests for publishing messages. To run this test use following command: +`CreateAndRetrieveThings` contains load tests for creating and retrieving things. +Execute the following command to run the suite: ```bash -sbt "gatling:testOnly com.mainflux.loadtest.simulations.PublishSimulation" +sbt "gatling:testOnly com.mainflux.loadtest.CreateAndRetrieveThings" ``` -### Create And Retrieve Client Simulation +### Message publishing -`CreateAndRetrieveClientSimulation` contains load tests for creating and retrieving clients. To run this test use following command: +`PublishMessages` contains load tests for publishing messages. Execute the following +command to run the suite: ```bash -sbt "gatling:testOnly com.mainflux.loadtest.simulations.CreateAndRetrieveClientSimulation" +sbt "gatling:testOnly com.mainflux.loadtest.PublishMessages" ``` diff --git a/load-test/src/test/scala/com/mainflux/loadtest/Constants.scala b/load-test/src/test/scala/com/mainflux/loadtest/Constants.scala new file mode 100644 index 00000000..55ef75be --- /dev/null +++ b/load-test/src/test/scala/com/mainflux/loadtest/Constants.scala @@ -0,0 +1,8 @@ +package com.mainflux.loadtest + +object Constants { + val UsersURL: String = System.getProperty("users", "http://localhost:8180") + val ThingsURL: String = System.getProperty("things", "http://localhost:8182") + val HttpAdapterURL: String = System.getProperty("http", "http://localhost:8185") + val RequestsPerSecond: Double = Integer.getInteger("requests", 100).toDouble +} \ No newline at end of file diff --git a/load-test/src/test/scala/com/mainflux/loadtest/CreateAndRetrieveThings.scala b/load-test/src/test/scala/com/mainflux/loadtest/CreateAndRetrieveThings.scala new file mode 100644 index 00000000..b398124c --- /dev/null +++ b/load-test/src/test/scala/com/mainflux/loadtest/CreateAndRetrieveThings.scala @@ -0,0 +1,30 @@ +package com.mainflux.loadtest + +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder + +import scala.concurrent.duration._ + +final class CreateAndRetrieveThings extends TestCase { + override def prepareAndExecute(): SetUp = { + val token = authenticate() + val thing = """{"type":"device", "name":"weio"}""" + + val scn = scenario("create and retrieve things") + .exec(http("create thing") + .post("/clients") + .header(HttpHeaderNames.ContentType, jsonType) + .header(HttpHeaderNames.Authorization, token) + .body(StringBody(thing)) + .check(status.is(201)) + .check(headerRegex(HttpHeaderNames.Location, "(.*)").saveAs("location"))) + .exec(http("retrieve thing") + .get("${location}") + .header(HttpHeaderNames.Authorization, token) + .check(status.is(200))) + + setUp(scn.inject(constantUsersPerSec(RequestsPerSecond) during 15.seconds)).protocols(httpProtocol(ThingsURL)) + } +} + diff --git a/load-test/src/test/scala/com/mainflux/loadtest/PublishMessages.scala b/load-test/src/test/scala/com/mainflux/loadtest/PublishMessages.scala new file mode 100644 index 00000000..77a42626 --- /dev/null +++ b/load-test/src/test/scala/com/mainflux/loadtest/PublishMessages.scala @@ -0,0 +1,80 @@ +package com.mainflux.loadtest + +import io.circe._ +import io.circe.parser._ +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder +import scalaj.http.Http + +import scala.concurrent.duration._ + +final class PublishMessages extends TestCase { + override def prepareAndExecute(): SetUp = { + def makeThing(token: String): (String, String) = { + val thing = """{"type":"device", "name":"weio"}""" + + val id = Http(s"$ThingsURL/clients") + .postData(thing) + .header(HttpHeaderNames.Authorization, token) + .header(HttpHeaderNames.ContentType, jsonType) + .asString + .headers(HttpHeaderNames.Location)(0).split("/")(2) + + val res = Http(s"$ThingsURL/things/$id") + .header(HttpHeaderNames.Authorization, token) + .header(HttpHeaderNames.ContentType, jsonType) + .asString + .body + + val key = parse(res).getOrElse(Json.Null).hcursor.downField("key").as[String].getOrElse("") + + (id, key) + } + + def makeChannel(token: String): String = { + val channel = """{"name":"mychan"}""" + + Http(s"$ThingsURL/channels") + .postData(channel) + .header(HttpHeaderNames.Authorization, token) + .header(HttpHeaderNames.ContentType, jsonType) + .asString + .headers(HttpHeaderNames.Location)(0) + .split("/")(2) + } + + def connect(channel: String, thing: String, token: String): Unit = { + Http(s"$ThingsURL/channels/$channel/things/$thing") + .method("PUT") + .header(HttpHeaderNames.Authorization, token) + .asString + } + + val message = + """ + |[ + | {"bn":"some-base-name:","bt":1.276020076001e+09,"bu":"A","bver":5,"n":"voltage","u":"V","v":120.1}, + | {"n":"current","t":-5,"v":1.2}, + | {"n":"current","t":-4,"v":1.3} + |]""".stripMargin + + val token = authenticate() + val (thingID, thingKey) = makeThing(token) + val channelID = makeChannel(token) + + connect(channelID, thingID, thingKey) + + val scn = scenario("publish message") + .exec(http("publish message request") + .post(s"/channels/$channelID/messages") + .header(HttpHeaderNames.ContentType, "application/senml+json") + .header(HttpHeaderNames.Authorization, thingKey) + .body(StringBody(message)) + .check(status.is(202))) + + setUp(scn.inject(constantUsersPerSec(RequestsPerSecond) during 15.seconds)).protocols(httpProtocol(url())) + } + + private def url(): String = System.getProperty("http", "http://localhost:8185") +} diff --git a/load-test/src/test/scala/com/mainflux/loadtest/TestCase.scala b/load-test/src/test/scala/com/mainflux/loadtest/TestCase.scala new file mode 100644 index 00000000..a9db8b09 --- /dev/null +++ b/load-test/src/test/scala/com/mainflux/loadtest/TestCase.scala @@ -0,0 +1,48 @@ +package com.mainflux.loadtest + +import io.circe._ +import io.circe.parser._ +import io.gatling.core.Predef._ +import io.gatling.http.Predef._ +import io.gatling.http.protocol.HttpProtocol +import scalaj.http.Http + +trait TestCase extends Simulation { + protected lazy val UsersURL: String = System.getProperty("users", "http://localhost:8180") + protected lazy val ThingsURL: String = System.getProperty("things", "http://localhost:8182") + protected lazy val RequestsPerSecond: Double = Integer.getInteger("requests", 100).toDouble + + protected val jsonType: String = "application/jsonType" + + def authenticate(): String = { + val user = """{"email":"john.doe@email.com", "password":"123"}""" + val headerName = HttpHeaderNames.ContentType + val contentType = jsonType + + Http(s"$UsersURL/users") + .postData(user) + .header(headerName, contentType) + .asString + + val res = Http(s"$UsersURL/tokens") + .postData(user) + .header(headerName, contentType) + .asString + .body + + val cursor = parse(res).getOrElse(Json.Null).hcursor + cursor.downField("token").as[String].getOrElse("") + } + + def httpProtocol(url: String): HttpProtocol = http + .baseURL(url) + .inferHtmlResources() + .acceptHeader("*/*") + .contentTypeHeader(jsonType) + .userAgentHeader("curl/7.54.0") + .build + + def prepareAndExecute(): SetUp + + prepareAndExecute() +} diff --git a/load-test/src/test/scala/com/mainflux/loadtest/simulations/Constants.scala b/load-test/src/test/scala/com/mainflux/loadtest/simulations/Constants.scala deleted file mode 100644 index 7664b393..00000000 --- a/load-test/src/test/scala/com/mainflux/loadtest/simulations/Constants.scala +++ /dev/null @@ -1,8 +0,0 @@ -package com.mainflux.loadtest.simulations - -object Constants { - val UsersUrl = System.getProperty("users", "http://localhost:8180") - val ClientsUrl = System.getProperty("clients", "http://localhost:8182") - val HttpAdapterUrl = System.getProperty("http", "http://localhost:8185") - val RequestsPerSecond = Integer.getInteger("requests", 100) -} \ No newline at end of file diff --git a/load-test/src/test/scala/com/mainflux/loadtest/simulations/CreateAndRetrieveClientSimulation.scala b/load-test/src/test/scala/com/mainflux/loadtest/simulations/CreateAndRetrieveClientSimulation.scala deleted file mode 100644 index 0fa4244b..00000000 --- a/load-test/src/test/scala/com/mainflux/loadtest/simulations/CreateAndRetrieveClientSimulation.scala +++ /dev/null @@ -1,65 +0,0 @@ -package com.mainflux.loadtest.simulations - -import scala.concurrent.duration._ -import scalaj.http.Http -import io.gatling.core.Predef._ -import io.gatling.http.Predef._ -import io.gatling.jdbc.Predef._ -import io.circe._ -import io.circe.generic.auto._ -import io.circe.parser._ -import io.circe.syntax._ -import CreateAndRetrieveClientSimulation._ -import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol -import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder -import com.mainflux.loadtest.simulations.Constants._ - -class CreateAndRetrieveClientSimulation extends Simulation { - - // Register user - Http(s"${UsersUrl}/users") - .postData(User) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - - // Login user - val tokenRes = Http(s"${UsersUrl}/tokens") - .postData(User) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - .body - - val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor - val token = tokenCursor.downField("token").as[String].getOrElse("") - - // Prepare testing scenario - val httpProtocol = http - .baseURL(ClientsUrl) - .inferHtmlResources() - .acceptHeader("*/*") - .contentTypeHeader(ContentType) - .userAgentHeader("curl/7.54.0") - - val scn = scenario("CreateAndGetClient") - .exec(http("CreateClientRequest") - .post("/clients") - .header(HttpHeaderNames.ContentType, ContentType) - .header(HttpHeaderNames.Authorization, token) - .body(StringBody(Client)) - .check(status.is(201)) - .check(headerRegex(HttpHeaderNames.Location, "(.*)").saveAs("location"))) - .exec(http("GetClientRequest") - .get("${location}") - .header(HttpHeaderNames.Authorization, token) - .check(status.is(200))) - - setUp( - scn.inject( - constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol) -} - -object CreateAndRetrieveClientSimulation { - val ContentType = "application/json" - val User = """{"email":"john.doe@email.com", "password":"123"}""" - val Client = """{"type":"device", "name":"weio"}""" -} \ No newline at end of file diff --git a/load-test/src/test/scala/com/mainflux/loadtest/simulations/PublishSimulation.scala b/load-test/src/test/scala/com/mainflux/loadtest/simulations/PublishSimulation.scala deleted file mode 100644 index aed27a9c..00000000 --- a/load-test/src/test/scala/com/mainflux/loadtest/simulations/PublishSimulation.scala +++ /dev/null @@ -1,98 +0,0 @@ -package com.mainflux.loadtest.simulations - -import scala.concurrent.duration._ -import scalaj.http.Http -import io.gatling.core.Predef._ -import io.gatling.http.Predef._ -import io.gatling.jdbc.Predef._ -import io.circe._ -import io.circe.generic.auto._ -import io.circe.parser._ -import io.circe.syntax._ -import PublishSimulation._ -import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol -import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder -import com.mainflux.loadtest.simulations.Constants._ - -class PublishSimulation extends Simulation { - - // Register user - Http(s"${UsersUrl}/users") - .postData(User) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - - // Login user - val tokenRes = Http(s"${UsersUrl}/tokens") - .postData(User) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - .body - - val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor - val token = tokenCursor.downField("token").as[String].getOrElse("") - - // Register client - val clientLocation = Http(s"${ClientsUrl}/clients") - .postData(Client) - .header(HttpHeaderNames.Authorization, token) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - .headers.get("Location").get(0) - - val clientId = clientLocation.split("/")(2) - - // Get client key - val clientRes = Http(s"${ClientsUrl}/clients/${clientId}") - .header(HttpHeaderNames.Authorization, token) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - .body - - val clientCursor = parse(clientRes).getOrElse(Json.Null).hcursor - val clientKey = clientCursor.downField("key").as[String].getOrElse("") - - // Register channel - val chanLocation = Http(s"${ClientsUrl}/channels") - .postData(Channel) - .header(HttpHeaderNames.Authorization, token) - .header(HttpHeaderNames.ContentType, ContentType) - .asString - .headers.get("Location").get(0) - - val chanId = chanLocation.split("/")(2) - - // Connect client to channel - Http(s"${ClientsUrl}/channels/${chanId}/clients/${clientId}") - .method("PUT") - .header(HttpHeaderNames.Authorization, token) - .asString - - // Prepare testing scenario - val httpProtocol = http - .baseURL(HttpAdapterUrl) - .inferHtmlResources() - .acceptHeader("*/*") - .contentTypeHeader("application/json; charset=utf-8") - .userAgentHeader("curl/7.54.0") - - val scn = scenario("PublishMessage") - .exec(http("PublishMessageRequest") - .post(s"/channels/${chanId}/messages") - .header(HttpHeaderNames.ContentType, "application/senml+json") - .header(HttpHeaderNames.Authorization, clientKey) - .body(StringBody(Message)) - .check(status.is(202))) - - setUp( - scn.inject( - constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol) -} - -object PublishSimulation { - val ContentType = "application/json" - val User = """{"email":"john.doe@email.com", "password":"123"}""" - val Client = """{"type":"device", "name":"weio"}""" - val Channel = """{"name":"mychan"}""" - val Message = """[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]""" -}