In my previous blog post we had a look at the tools that we are going to use for automated testing of WSO2 ESB / WSO2 EI services. In this blog, I’ll test a service (REST API) which transforms a REST request into a SOAP request, invokes a back-end service stub and finally transforms the SOAP response into a REST response. After all this blog is not about writing nice meaningful services, it is about testing services. I will deep dive into the Jenkinsfile and I’ll explain the service I’m testing.
Some background information
The service to test
Before we deep dive into the Jenkinsfile, I’ll explain the service I’m testing:
On the WSO2 ESB / WSO2 EI I deployed a REST API which uses a backend service called MockinBirdSOAP, the API contains two resources getEnglish and getLatin. These resources match the two operations of the SOAP service:
- getEnglishName which translates the Latin name of a mocking bird into its English name.
- getLatinName wich translates the English name of a mocking bird into its Latin name.
The testscript
The testscript performs a few tests:
- call ${#Project#esb.base.url}/api/mockingbirds/getEnglish/mimus%20dorsalis
and check if ‘Brown-backed mockingbird’ is returned - call ${#Project#esb.base.url}/api/mockingbirds/getEnglish/unknown
and check if ‘–Unknown–’ is returned - call ${#Project#esb.base.url}/api/mockingbirds/getLatin/Bahama mockingbird
and check if ‘Mimus gundlachii’ is returned - call ${#Project#esb.base.url}/api/mockingbirds/getLatin/unknown
and check if ‘–Unknown–’ is returned
The interesting part
Now we know the products and the test script we can finally get to the interesting part.
The setup
I have setup an environment in which I have Jenkins and docker running on the build server machine and the EI/ESB on another machine. When Jenkins detects a new commit on the git repository, it performs a checkout and it will read the Jenkinsfile in the repository to determine what it must do. This Jenkinsfile contains some standard parts:
pipeline {
agent any
options {
disableConcurrentBuilds()
}
tools {
maven 'Maven 3.3.9'
jdk 'jdk8'
}
stages {
…
}
}
- agent any tells Jenkins it can execute on any available agent.
- disableConcurrentBuilds() disallows concurrent executions of the pipeline.
- tools in this section you specify the versions of the tools that are used in the pipeline.
In this case Jenkins is told to use ‘Maven 3.3.9’ and ‘jdk8’. In the global config section of Jenkins you can configure the tools and assign a label to a certain version of a tool, in this case ‘Maven 3.3.9 will point to Maven 3.3.9 and jdk8 to jdk 8.xxx. - stages In the stages section stages are defined which will be executed sequentially by default. (There is support for parallel execution of stages/steps, to speed up your builds, but I will not discuss that in this blog since parallelization always makes things more complicated and its not what this blog is about.)
The stages
The real fun start with the stages, here I tell Jenkins what to do. In my setup, I have created the following stages:
- Init: Here some variables are set which are used in the other stages
- Build: Here the ESB artifacts are built. The stub services could be built here as well.
- Deploy: The ESB Artifacts are deployed on the ESB. (other artifacts can be deployed on other servers if needed.)
- Test: SoapUI is used to perform some unit tests on the ESB services, against stub services. These stub services are deployed on a tomcat instance running in docker container.
Init stage
stage('init') {
steps {
script {
// Concurrent builds, should not try to use the same local port(s),
// so use the executor_number to get the port(s)
int port = 9090 + (env.EXECUTOR_NUMBER as Integer)
env.DOCKER_INSTANCE_PORT = port
// The hostname on which the stubbed services can be reached
env.DOCKER_INSTANCE_ADDR =
InetAddress.localHost.canonicalHostName
// Make sure we have a unique name for the docker instance
def din = "JNK_${JOB_NAME}_${BUILD_ID}"
din = din.replaceAll('/', '_')
env.DOCKER_INSTANCE_NAME = din
}
}
}
In this stage, some environment variables used in the pipeline are defined:
- DOCKER_INSTANCE_PORT: Eventhough I used the option disableConcurrentBuilds() to make sure that only one instance of this pipeline is executed at any moment, another pipeline may be executed by another worker on this Jenkins node. By adding the EXECUTER_NUMBER to a fixed value (9090) I make sure each Docker instance listens to its own host port (9090, 9091, 9092, …)
- DOCKER_INSTANCE_ADDR: I need to tell the ESB on which host the Docker instance will run, as this is the host the end points on the ESB will have to point to.
- DOCKER_INSTANE_NAME: When the tests are executed, I want to stop and destroy the Docker instance that hosted the stub services. This can easily be done by its name, so I also create a unique instance name for the Docker instance.
Build stage
stage('build') {
steps {
sh 'mvn clean install –Pdev -DmockServer.base.url =
"http://$DOCKER_INSTANCE_ADDR:$DOCKER_INSTANCE_PORT"'
}
}
In the build stage, I pass the hostname and port (the $variables) on which the stub services will be started to the maven command executed to build the ESB artifacts. Inside the pom.xml of the parent project the service in the profiles section, the base URL of the service is defined:
<
mockingBird.server.baseUrl>${mockServer.base.url}</mockingBird.server.baseUrl>
In the registry project, the address of the endpoint contains a so called placeholder:
<address uri="${mockingBird.server.baseUrl}/MockingBird-1.0.0/mockMockingBirdSOAP">
Here I use one of the features of the wso2 maven-tools used while building the car file: the replacement of placeholders by values which can be defined in several ways:
- On the command line, using the –D option of Java
- As properties defined in pom.xml, typically in a profile block in the parent projects pom.xml.
Deploy stage
stage('deploy') {
steps {
sh 'mvn deploy –Pdev -Dmaven.deploy.skip=true -Dmaven.car.deploy.skip=false
-DmockServer.base.url="http://$DOCKER_INSTANCE_ADDR:$DOCKER_INSTANCE_PORT"'
// ToDo: wait until deployment is finished ... this is a quick hack
sleep 10
}
}
In the deploy stage, I deploy the build artifacts on the test ESB. I don’t deploy these artifacts to the artifact repository (Nexus, Artifactory, …) yet. We only want to deploy the artifacts to a repository when they pass the tests.
Test stage
stage('test') {
steps {
// Start the docker instance
sh 'sudo docker run -d --name $DOCKER_INSTANCE_NAME -p $DOCKER_INSTANCE_PORT:8080
-v $WORKSPACE/mocks:/usr/local/tomcat/webapps tomcat:8.5-jre8-alpine'
// ToDo: wait until docker is up and running ... this is a quick hack
sleep 10
script {
try {
// Perform the tests
sh 'mvn -f test-pom.xml test -Desb.base.url="http://esb.local:80"'
} catch (err) {
echo "Failed: ${err}"
currentStage.result = 'FAILURE'
} finally {
// Stop and remove the docker instance
sh 'sudo docker stop $DOCKER_INSTANCE_NAME'
sh 'sudo docker rm $DOCKER_INSTANCE_NAME'
}
}
}
}
In the test stage, I perform the unit tests on services by running stub services on a tomcat instance running in a Docker container:
- Start the docker instance with:
- The previously determined host port mapped to port 8080 in the container.
- Deploy the stub services by mounting the stubs folder as tomcat/webapps.
- To give tomcat time to start and deploy the stub services, I wait a few seconds.
(It would be fancier to check if all services are available.) - I run the SoapUI testrunner to perform the tests.
- Stop the tomcat docker instance.
- Remove the tomcat docker instance.
Next steps in the pipeline
The pipeline used in this blog stops here, in a real project there will be more steps like:
- Build with dev profile
- Deploy the with dev profile built artifacts on the DEV environment.
(The DEV ESB will be connected to the DEV back end servers.) - Execute integration tests
- Build with tst profile
- Deploy the with tst profile built artifacts on the TST environment.
- Execute integration tests
- Build with acc profile
- Deploy the with acc profile built artifacts on the ACC environment.
- Execute integration tests
- Execute performance tests
- Build with prd profile
- After a manual promote action:
Deploy the with prd profile built artifacts on the PRD environment.
Which steps will or won’t be in your pipeline is up to you …
The big picture
This picture shows the steps performed by the Jenkins pipeline, as well as the different products / tools used in this setup.
If you have any questions about this blogpost contact us via the comments section of this blog. View also our WSO2 Tutorials, webinars or white papers for more technical information. Need support? We do deliver WSO2 Product Support, WSO2 Development Support, WSO2 Operational Support and WSO2 Training Programs.