Enterprise Java

Continuous Stress Testing for your JAX-RS (and JavaEE) applications with Gatling + Gradle + Jenkins Pipeline

In this post I am going to explain how to use Gatling project to write stress tests for your JAX-RS Java EE endpoints, and how to integrate them with Gradle and Jenkins Pipeline, so instead of having a simple stress tests, what you have is a continuous stress testing, where each commit might fire these kind of tests automatically, providing automatic assertions and more important graphical feedback of each execution so you can monitorize how the performance is evolving in your application.

First thing to develop is the JAX-RS JavaEE service:

  
@Path("/planet")
@Singleton
@Lock(LockType.READ)
public class PlanetResources {

    @Inject
    SwapiGateway swapiGateway;

    @Inject
    PlanetService planetService;

    @Inject
    @AverageFormatter
    DecimalFormat averageFormatter;

    @GET
    @Path("/orbital/average")
    @Produces(MediaType.TEXT_PLAIN)
    @Asynchronous
    public void calculateAverageOfOrbitalPeriod(@Suspended final AsyncResponse response) {

        // Timeout control
        response.setTimeoutHandler(asyncResponse -> asyncResponse.resume(Response.status
                (Response.Status.SERVICE_UNAVAILABLE)
                .entity("TIME OUT !").build()));
        response.setTimeout(30, TimeUnit.SECONDS);

        try {
            // SwapiGateway is an interface to swapi.co (Star Wars API)
            JsonObject planets = swapiGateway.getAllPlanets();
            final JsonArray results = planets.getJsonArray("results");
            
            // Make some calculations with the result retrieved from swapi.co
            double average = planetService.calculateAverageOfOrbitalPeriod(results);
            final Response averageResponse = Response.ok(
                    averageFormatter.format(average))
                  .build();
            response.resume(averageResponse);

        } catch(Throwable e) {
            response.resume(e);
        }
    }
} 

There is nothing special, this is an asynchronous JAX-RS endpoint that connects to swapi.co site, retrieves all the information of Star Wars planets, calculates the average of orbital period and finally it returns it in form of text. For sake of simplicity, I am not going to show you all the other classes but they are quite simple and at the end of the post I will provide you the github repository.

The application is packaged inside a war file and deployed into an application server. In this case into an Apache TomEE 7 deployed inside the official Apache TomEE Docker image.

Next step is configuring Gradle build script with Gatling dependencies. Since Gatling is written in Scala you need to use Scala plugin.

  
 apply plugin: 'java'
apply plugin: 'scala'

def gatlingVersion = "2.1.7"

dependencies {
    compile "org.scala-lang:scala-library:2.11.7"
    testCompile "io.gatling:gatling-app:${gatlingVersion}"
    testCompile "io.gatling.highcharts:gatling-charts-highcharts:${gatlingVersion}"
} 

After that, it is time to write our first stress test. It is important to notice that writing stress tests for Gatling is writing a Scala class using the provided DSL. Even for people who has never seen Scala is pretty intuitive how to use it.

So create a directory called src/test/scala and create a new class called  AverageOrbitalPeriodSimulation.scala with next content:

  
 package org.starwars

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import scala.util.Properties

// Extends from Simulation
class AverageOrbitalPeriodSimulation extends Simulation {

  // Gets the base URL where our service is running from environment/system property
  val LOCATION_PROPERTY = "starwars_planets_url";
  val location = Properties.envOrElse(LOCATION_PROPERTY, 
                 Properties.propOrElse(LOCATION_PROPERTY, "http://localhost:8080/"))

  // configures the base URL
  val conf = http.baseURL(location)
  
  // defines the scenario to run, which in this case is a GET to endpoint defined in JAX-RS service
  val scn = scenario("calculate average orbital period")
    .exec(http("get average orbital period")
      .get("rest/planet/orbital/average"))
    .pause(1)

  // instead of simulating 10 users at once, it adds gradullay the 10 users during 3 seconds
  // asserts that there is no failing requests and that at max each request takes less than 3 seconds
  setUp(scn.inject(rampUsers(10).over(3 seconds)))
    .protocols(conf)
    .assertions(global.successfulRequests.percent.is(100), global.responseTime.max.lessThan(3000))
}

Every simulation must extends Simulation object. This simulation takes base URL of the service from starwars_planets_url environment or system property, it creates the scenario pointing to the endpoint defined in JAX-RS,  and finally during 3 seconds it will gradually add users until 10 users are running at the same time. The test will pass only if all the requests succeed in less than 3 seconds.

Now we need to run this test. You will notice that this is not a JUnit test, so you cannot do a Run As JUnit test. What you need to do is use a runnable class provided by Gatling which requires you pass as argument the simulation class. This is really easy to do with Gradle.

  
task runLoadTest(type: JavaExec) {
    // before runnign the task we need to compile the tests
    dependsOn testClasses
    description = 'Stress Test Calculating Orbital Period'
    classpath = sourceSets.main.runtimeClasspath + sourceSets.test.runtimeClasspath

    // if starwars_planets_url is not provided we add the DOCKER_HOST one automatically
    def starwarsUrl;
    if (!System.env.containsKey('starwars_planets_url') && !System.properties.containsKey('starwars_planets_url')) {
        if (System.env.containsKey('DOCKER_HOST')) {
            starwarsUrl = System.env.DOCKER_HOST.replace("tcp", "http").replace("2376", "9090") + "/starwars/"
        } else {
            starwarsUrl = "http://localhost:8080/starwars/"
        }
    }

    jvmArgs = [ "-Dgatling.core.directory.binaries=${sourceSets.test.output.classesDir.toString()}" ]

    // Means that the url has been calculated here and we set it
    if (starwarsUrl != null) {
        environment["starwars_planets_url"] = starwarsUrl
    }

    // Gatling application
    main = "io.gatling.app.Gatling"


    // Specify the simulation to run and output
    args = [
            "--simulation", "org.starwars.AverageOrbitalPeriodSimulation",
            "--results-folder", "${buildDir}/reports/gatling-results",
            "--binaries-folder", sourceSets.test.output.classesDir.toString(),
            "--output-name", "averageorbitalperiodsimulation",
            "--bodies-folder", sourceSets.test.resources.srcDirs.toList().first().toString() + "/gatling/bodies",
    ]
}

// when running test task we want to execute the Gatling test
test.dependsOn runLoadTest 

We are defining a Gradle task of type JavaExec, since what we want is to run a runnable class. Then we make the life a bit easier for developer by automatically detect that if starwars_planets_url is not set, we are running this test into a machine that has Docker installed so probably this is the host to be used.

Finally we override the environment variable if it is required, we set the runnable class with required properties and we configure Gradle to execute this task every time the test task is executed ( ./gradlew test).

If you run it, you might see some output messages from Gatling, and after all a message like: please open the following file: /Users/…./stress-test/build/reports/gatling results/averageorbitalperiodsimulation-1459413095563/index.html and this is where you can get the report. Notice that a random number is appended at the end of the directory and this is important as we are going to see later. The report might looks like:

Screen Shot 2016-03-31 at 10.36.15

At this time we have Gatling integrated with Gradle, but there is a missing piece here, and it is adding the continuous part on the equation. For adding continuous stress testing we are going to use Jenkins and Jenkins Pipeline as CI server so for each commit stress tests are executed among other tasks such as compile, run unit, integration tests, or code quality gate.

Historically Jenkins jobs were configured using web UI, requiring users to manually create jobs, fill the details of the job and create the pipeline through web browser. Also this makes keeping configuration of the job separated from the actual code being built.

With the introduction of Jenkins Pipeline plugin. This plugin is a Groovy DSL that let’s implement you the entire build process in a file and store that alongside its code. Jenkins 2.0 comes by default with this plugin, but if you are using Jenkins 1.X you can install it as any other plugin ( https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Plugin)

So now we can start coding our release plugin but for the purpose of this post only stress part is going to be covered. You need to create a file called Jenkinsfile (the name is not mandatory but it is the de-facto name) on the root of your project, and in this case with next content:

  
 stage 'Compile And Unit Test'

stage 'Code Quality'

stage 'Integration Test'

stage 'Acceptance Test'

// defines an stage for info purposes
stage 'Stress Test'

def dockerHost = '...'
//defines a node to run the stage
node {
  // get source code from location where Jenkinsfile (this) is located.
  // you could use stash/unstash to get sources from previous stages instead of getting from SCM
  checkout scm
  // defines the environment variable for stress test
  withEnv(["starwars_planets_url=http://${dockerHost}:9090/starwars/"]) {
    // executes shell script
    sh './gradlew test'
  }
  
}

In this case we are defining a new stage which is called Stress Test. Stage step is only used as informative and it will be used for logging purposes. Next a node is defined. A node is a Jenkins executor where to execute the code. Inside this node, the source code is checked out from the same location where Jenkinsfile is placed, sets a new environment variable pointing out to the location where the application is deployed, and finally a shell step which executes the Gradle test task.

Last step in Jenkins is to create a new job of type Pipeline and set the location of the Jenkinsfile. So go to Jenkins > New Item > Pipeline and give a name to the job.

Screen Shot 2016-03-31 at 11.55.28

Then you only need to go to Pipeline section and configure the SCM repository where the project is stored.

Screen Shot 2016-03-31 at 11.56.30

And then if you have correctly configured the hooks from Jenkins and your SCM server, this job is going to be executed for every commit, so your stress tests are going to run continuously.

Of course probably you have noticed that stress tests are executed but no reports are published in Jenkins, so you have no way to see or compare results from different executions. For this reason you can use publishHtml plugin to store the generated reports in Jenkins. If you don’t have the plugin installed yet, you need to install it as any other Jenkins plugin.

PublishHtml plugin allows us to publish some html files generated by our build tool to Jenkins so they are available to users and also categorised by build number. You need to configure the location of the directory of files to publish, and here we find the first problem, do you remember that Gatling generates a directory with a random number? So we need to fix this first. You can follow different strategies, but the easiest one is simply rename the directory to a known static name after the tests.

Open Gradle build file and add next content.

  
 task(renameGatlingDirectory) << {
    // find the directory
    def report = {file -> file.isDirectory() && file.getName().startsWith('averageorbitalperiodsimulation')}
    def reportDirectory = new File("${buildDir}/reports/gatling-results").listFiles().toList()
    .findAll(report)
    .sort()
    .last()
    
    // rename to a known directory
    // should always work because in CI it comes from a clean execution
    reportDirectory.renameTo("${buildDir}/reports/gatling-results/averageorbitalperiodsimulation")
}

// it is run after test phase
test.finalizedBy renameGatlingDirectory

We are creating a new task executed at the end of test task that renames the last created directory to averageorbitalperiodsimulation.

Final step is add after shell call in Jenkinsfile next call:

publishHTML(target: [reportDir:'stress-test/build/reports/gatling-results/averageorbitalperiodsimulation', reportFiles: 'index.html', reportName: 'Gatling report', keepAll: true])

After that you might see a link in the job page that points to the report.

Screen Shot 2016-03-31 at 15.22.30

And that’s all, thanks of Gradle and Jenkins you can implement a continuous stress testing strategy in an easy way and just using code the language all developers speak.

We keep learning,

Alex.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Evgeny Shepelyuk
Evgeny Shepelyuk
8 years ago

You may make this task even a little bit easier by leveraging Gatling plugin for Gradle
https://github.com/lkishalmi/gradle-gatling-plugin

Back to top button