From d683a1624fa2fb1a44b11a17d3596b493524bcf5 Mon Sep 17 00:00:00 2001 From: Alien Ike Date: Sat, 18 Jan 2025 12:13:40 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20arquilli?= =?UTF-8?q?an/arquillian-cube@930597b9287faf3bdc9012f8d62ceea7afeabcaf=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 - 1.0.0.Alpha1/index.html | 6042 -------------------------------------- 1.0.0.Alpha10/index.html | 6042 -------------------------------------- 1.0.0.Alpha11/index.html | 6042 -------------------------------------- 1.0.0.Alpha12/index.html | 6042 -------------------------------------- 1.0.0.Alpha13/index.html | 6042 -------------------------------------- 1.0.0.Alpha14/index.html | 6042 -------------------------------------- 1.0.0.Alpha15/index.html | 6042 -------------------------------------- 1.0.0.Alpha16/index.html | 6042 -------------------------------------- 1.0.0.Alpha17/index.html | 6042 -------------------------------------- 1.0.0.Alpha18/index.html | 6042 -------------------------------------- 1.0.0.Alpha19/index.html | 6042 -------------------------------------- 1.0.0.Alpha2/index.html | 6042 -------------------------------------- 1.0.0.Alpha20/index.html | 6042 -------------------------------------- 1.0.0.Alpha3/index.html | 6042 -------------------------------------- 1.0.0.Alpha4/index.html | 6042 -------------------------------------- 1.0.0.Alpha5/index.html | 6042 -------------------------------------- 1.0.0.Alpha6/index.html | 6042 -------------------------------------- 1.0.0.Alpha7/index.html | 6042 -------------------------------------- 1.0.0.Alpha8/index.html | 6042 -------------------------------------- 1.0.0.Alpha9/index.html | 6042 -------------------------------------- 1.0.0/index.html | 6042 -------------------------------------- 1.0.1/index.html | 6042 -------------------------------------- 1.1.0/index.html | 6042 -------------------------------------- 1.2.0/index.html | 6042 -------------------------------------- 1.3.0/index.html | 6042 -------------------------------------- 1.3.2/index.html | 6042 -------------------------------------- 1.4.0/index.html | 6042 -------------------------------------- 1.5.0/index.html | 6042 -------------------------------------- 1.5.1/index.html | 6042 -------------------------------------- 1.6.0/index.html | 6042 -------------------------------------- 1.7.0/index.html | 6042 -------------------------------------- 1.7.1/index.html | 6042 -------------------------------------- 1.8.0/index.html | 6042 -------------------------------------- index.html | 1772 +++++------ 35 files changed, 785 insertions(+), 200375 deletions(-) delete mode 100644 .gitignore delete mode 100644 1.0.0.Alpha1/index.html delete mode 100644 1.0.0.Alpha10/index.html delete mode 100644 1.0.0.Alpha11/index.html delete mode 100644 1.0.0.Alpha12/index.html delete mode 100644 1.0.0.Alpha13/index.html delete mode 100644 1.0.0.Alpha14/index.html delete mode 100644 1.0.0.Alpha15/index.html delete mode 100644 1.0.0.Alpha16/index.html delete mode 100644 1.0.0.Alpha17/index.html delete mode 100644 1.0.0.Alpha18/index.html delete mode 100644 1.0.0.Alpha19/index.html delete mode 100644 1.0.0.Alpha2/index.html delete mode 100644 1.0.0.Alpha20/index.html delete mode 100644 1.0.0.Alpha3/index.html delete mode 100644 1.0.0.Alpha4/index.html delete mode 100644 1.0.0.Alpha5/index.html delete mode 100644 1.0.0.Alpha6/index.html delete mode 100644 1.0.0.Alpha7/index.html delete mode 100644 1.0.0.Alpha8/index.html delete mode 100644 1.0.0.Alpha9/index.html delete mode 100644 1.0.0/index.html delete mode 100644 1.0.1/index.html delete mode 100644 1.1.0/index.html delete mode 100644 1.2.0/index.html delete mode 100644 1.3.0/index.html delete mode 100644 1.3.2/index.html delete mode 100644 1.4.0/index.html delete mode 100644 1.5.0/index.html delete mode 100644 1.5.1/index.html delete mode 100644 1.6.0/index.html delete mode 100644 1.7.0/index.html delete mode 100644 1.7.1/index.html delete mode 100644 1.8.0/index.html diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4befed30a..000000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.DS_Store -.idea diff --git a/1.0.0.Alpha1/index.html b/1.0.0.Alpha1/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha1/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha10/index.html b/1.0.0.Alpha10/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha10/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha11/index.html b/1.0.0.Alpha11/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha11/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha12/index.html b/1.0.0.Alpha12/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha12/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha13/index.html b/1.0.0.Alpha13/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha13/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha14/index.html b/1.0.0.Alpha14/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha14/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha15/index.html b/1.0.0.Alpha15/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha15/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha16/index.html b/1.0.0.Alpha16/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha16/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha17/index.html b/1.0.0.Alpha17/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha17/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha18/index.html b/1.0.0.Alpha18/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha18/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha19/index.html b/1.0.0.Alpha19/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha19/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha2/index.html b/1.0.0.Alpha2/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha2/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha20/index.html b/1.0.0.Alpha20/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha20/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha3/index.html b/1.0.0.Alpha3/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha3/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha4/index.html b/1.0.0.Alpha4/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha4/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha5/index.html b/1.0.0.Alpha5/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha5/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha6/index.html b/1.0.0.Alpha6/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha6/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha7/index.html b/1.0.0.Alpha7/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha7/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha8/index.html b/1.0.0.Alpha8/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha8/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0.Alpha9/index.html b/1.0.0.Alpha9/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0.Alpha9/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.0/index.html b/1.0.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.0.1/index.html b/1.0.1/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.0.1/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.1.0/index.html b/1.1.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.1.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.2.0/index.html b/1.2.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.2.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.3.0/index.html b/1.3.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.3.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.3.2/index.html b/1.3.2/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.3.2/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.4.0/index.html b/1.4.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.4.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.5.0/index.html b/1.5.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.5.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.5.1/index.html b/1.5.1/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.5.1/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.6.0/index.html b/1.6.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.6.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.7.0/index.html b/1.7.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.7.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.7.1/index.html b/1.7.1/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.7.1/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/1.8.0/index.html b/1.8.0/index.html deleted file mode 100644 index b1d7b9b0b..000000000 --- a/1.8.0/index.html +++ /dev/null @@ -1,6042 +0,0 @@ - - - - - - - -Arquillian Cube - - - - - - - -
-
-
-
-

Build Status

-
-
- - - - - -
- - -1.0.0.Alpha7 breaks incompatibility with previous versions in some cases. The major difference is that instead of using the boot2docker keyword to refer to the auto resolved boot2docker ip in the serverUri parameter, you should now used dockerHost. -
-
-
- - - - - -
- - -1.0.0.Alpha13 changes default format from Cube to Docker Compose. In case you are using Cube format you need to update arquillian.xml with <property name="definitionFormat">CUBE</property> -
-
-
-
-
-

1. What is this?

-
-
-

Arquillian Cube is an Arquillian extension that can be used to manager Docker containers from Arquillian.

-
-
-

Extension is named Cube for two reasons:

-
-
-
    -
  • -

    Because Docker is like a cube

    -
  • -
  • -

    Because Borg starship is named cube and well because we are moving tests close to production we can say that "any resistance is futile, bugs will be assimilated".

    -
  • -
-
-
-

With this extension you can start a Docker container with a server installed, deploy the required deployable file within it and execute Arquillian tests.

-
-
-

The key point here is that if Docker is used as deployable platform in production, your tests are executed in a the same container as it will be in production, so your tests are even more real than before.

-
-
-

But it also lets you start a container with every required service like database, mail server, …​ and instead of stubbing or using fake objects your tests can use real servers.

-
-
- - - - - -
- - -
-

This extension has been developed and tested on a Linux machine with the Docker server already installed. -It works with Boot2Docker as well in Windows and MacOS machines, but some parameters like host ip must be the Boot2Docker server instead of localhost (in case you have Docker server installed inside your own machine).

-
-
-

One of the best resources to learn about why using Boot2Docker is different from using Docker in Linux can be read here http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide

-
-
-
-
-
-
-

2. Preliminaries

-
-
-

Arquillian Cube relies on docker-java API.

-
-
-

To use Arquillian Cube you need a Docker daemon running on a computer (it can be local or not), but probably it will be at local.

-
-
-

By default the Docker server uses UNIX sockets for communicating with the Docker client. Arquillian Cube will attempt to detect the operating system it is running on and either set docker-java to use UNIX socket on Linux or to Boot2Docker on Windows/Mac as the default URI.

-
-
-

If you want to use TCP/IP to connect to the Docker server, you’ll need to make sure that your Docker server is listening on TCP port. -To allow Docker server to use TCP add the following line to /etc/default/docker:

-
-
-

DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"

-
-
-

After restarting the Docker daemon you need to make sure that Docker is up and listening on TCP.

-
-
-
-
$ docker -H tcp://127.0.0.1:2375 version
-
-Client version: 0.8.0
-Go version (client): go1.2
-Git commit (client): cc3a8c8
-Server version: 1.2.0
-Git commit (server): fa7b24f
-Go version (server): go1.3.1
-
-
-
-

If you cannot see the client and server versions then it means that something is wrong with the Docker installation.

-
-
-
-
-

3. Basic Example

-
-
-

After having a Docker server installed we can start using Arquillian Cube. -In this case we are going to use a very simple example using a Docker image with Apache Tomcat and we are going to test a Servlet on it.

-
-
-
HelloWorldServlet.java
-
-
@WebServlet("/HelloWorld")
-public class HelloWorldServlet extends HttpServlet {
-
-  @Override
-  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    PrintWriter writer = resp.getWriter();
-    writer.println("Hello World");
-  }
-}
-
-
-
-
HelloWorldServletTest.java
-
-
@RunWith(Arquillian.class)
-public class HelloWorldServletTest {
-
-  @Deployment(testable=false) (1)
-  public static WebArchive create() {
-    return ShrinkWrap.create(WebArchive.class, "hello.war").addClass(HelloWorldServlet.class); (2)
-  }
-
-  @Test
-  public void should_parse_and_load_configuration_file(@ArquillianResource URL resource) throws IOException { (3)
-
-    URL obj = new URL(resource, "HelloWorld");
-    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
-    con.setRequestMethod("GET");
-
-    BufferedReader in = new BufferedReader(
-            new InputStreamReader(con.getInputStream()));
-    String inputLine;
-    StringBuffer response = new StringBuffer();
-
-    while ((inputLine = in.readLine()) != null) {
-        response.append(inputLine);
-    }
-    in.close();
-
-    assertThat(response.toString(), is("Hello World"));(4)
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - -
1In this case we are running the test as a client. So in fact this test is executed against the container instead of inside the container.
2No changes in this part, we need to create a deployable file, and because we are testing against Tomcat, a war file is created.
3Because the test is run as client, we can use @ArquillianResource to get the URL where the file is deployed. Note that this will be the URL to access Tomcat running inside the Docker container.
4Typical jUnit assertion of servlet response.
-
-
-

Now this test could be run in any container, there is nothing that ties this to Docker. -Next step is adding some dependencies apart from the typical Arquillian dependencies.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-docker</artifactId> (1)
-  <version>${project.version}</version>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.arquillian.container</groupId>
-  <artifactId>arquillian-tomcat-remote-7</artifactId> (2)
-  <version>1.0.0.CR7</version>
-  <scope>test</scope>
-</dependency>
-
-
-
- - - - - - - - - -
1Adds Arquillian Cube dependency.
2From the point of view of Arquillian, Tomcat is being executed in a remote host (in fact this is true because Tomcat is running inside Docker which is external to Arquillian), so we need to add the remote adapter.
-
-
-

And finally we need to configure Tomcat remote adapter and Arquillian Cube in arquillian.xml file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker"> (1)
-      <property name="serverVersion">1.12</property> (2)
-      <property name="serverUri">http://localhost:2375</property> (3)
-      <property name="definitionFormat">CUBE</property>
-      <property name="dockerContainers"> (4)
-          tomcat:
-            image: tutum/tomcat:7.0
-            exposedPorts: [8089/tcp]
-            await:
-              strategy: polling
-            env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-            portBindings: [8089/tcp, 8080/tcp]
-      </property>
-  </extension>
-
-  <container qualifier="tomcat" default="true"> (5)
-      <configuration>
-          <property name="host">localhost</property> (6)
-          <property name="httpPort">8080</property> (7)
-          <property name="user">admin</property> (8)
-          <property name="pass">mypass</property>
-      </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1Arquillian Cube extension is registered.
2Docker server version is required.
3Docker server URI is required. In case you are using a remote Docker host or Boot2Docker here you need to set the remote host ip, but in this case Docker server is on same machine.
4A Docker container contains a lot of parameters that can be configured. To avoid having to create one XML property for each one, a YAML content can be embedded directly as property.
5Configuration of Tomcat remote adapter. Cube will start the Docker container when it is ran in the same context as an Arquillian container with the same name.
6Host can be localhost because there is a port forwarding between container and Docker server.
7Port is exposed as well.
8User and password are required to deploy the war file to remote Tomcat.
-
-
-

Notice that in this case you are using CUBE format as orchestration definition but you’ll see in next sections that you can use docker_compose too. -Read more at Configuration section and Docker-Compose Format.

-
-
-

And that’s all. -Now you can run your test and you will see how tutum/tomcat:7.0 image is downloaded and started. -Ports 8080 (Tomcat standard port) and 8089(JMX port used by Arquillian) are exposed. -Finally in env section, environment variables are set. Read next link to understand why this is required https://docs.jboss.org/author/display/ARQ/Tomcat+7.0+-+Remote

-
-
-
-
-

4. Standalone Example

-
-
-

In previous example you have seen how to to start a Docker container, deploy an Shrinkwrap element using @Deployment to finally execute the test inside Docker container, get the results and stops everything.

-
-
-

But this is not the only way of using Arquillian Cube. -Sometimes you already have your container image created and you want to test that it starts, that exposes correctly the ports and of course that for example the deployment file is in the correct place. -This is known as Container tests. -Notice that in this case you don’t need @Deployment method since the image is already created and you don’t want to modify anything. -The only thin g that you want is start the container, run the test (as client) and if everything works, then just stop the container.

-
-
-

Let’s see how to do it:

-
-
-

First major change is in dependencies:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1Instead of setting a container, you set arquillian in standalone mode
-
-
-

Than you can define in your arquillian.xml or docker-compose.xml containers. -For example:

-
-
-
docker-compose.yml
-
-
planetstest:
-  extends:
-      file: ../docker-compose.yml
-      service: planets
-  image: lordofthejars/starwars:1.0.1 #1
-
-
-
-

<1>Image with WAR file already bundled inside server

-
-
-

And finally the test:

-
-
-
PlanetServiceAPIContainerTest.java
-
-
@RunWith(Arquillian.class)
-public class PlanetServiceAPIContainerTest {
-
-    @HostIp
-    private String ip;
-
-    @HostPort(containerName = "planetstest", value = 8080)
-    int planetsPort;
-
-    @Test
-    public void shouldReturnAverage() {
-        URL url = new URL("http://" + ip + ":" + planetsPort + "/starwars/");
-        final String average = RestAssured.get(url.toExternalForm() + "rest/planet/orbital/average").asString();
-        assertThat(average, is("1699.42"));
-    }
-
-
-
-

Notice that in this case there is no @Deployment method because the full application is already bundled in the docker image that is being used. -This test basically validates that the image can be started, and finally that from outside you can communicate with it and it returns something valid.

-
-
-
-
-

5. Arquillian Cube BOM - Unified Dependencies

-
-
-

This aims to fulfill requirements of unify naming & versions.

-
-
-

5.1. Usage

-
-

Include the following snippet in your pom.xml file:

-
-
-
pom.xml
-
-
<properties>
-    <version.arquillian_cube>${latest_released_version}</version.arquillian_cube>
-</properties>
-
-<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.arquillian.cube</groupId>
-            <artifactId>arquillian-cube-bom</artifactId>
-            <version>${version.arquillian_cube}</version>
-            <scope>import</scope>
-            <type>pom</type>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-
-
-

Then include the individual modules as you see fit, by simply depending on the unified pom name:

-
-
-
-
<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker</artifactId>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
-
-

5.2. Available Modules:

-
-

5.2.1. Arquillian Cube API

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-api</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.2. Arquillian Cube SPI

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-spi</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.3. Arquillian Cube Core

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-core</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.4. Arquillian Cube Containerless

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-containerless</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.5. Arquillian Cube Requirement

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.6. Arquillian Cube Docker

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.7. Arquillian Cube Docker Junit Rule

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-junit-rule</artifactId>
-    <version>${project.version}</version>
-</dependency>
-
-
-
-
-

5.2.8. Arquillian Cube Docker Drone

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-drone</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.9. Arquillian Cube Docker Restassured

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.10. Arquillian Cube Docker Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.11. Docker Java AssertJ

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>assertj-docker-java</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.12. Arquillian Cube Kubernetes

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.13. Arquillian Cube Kubernetes Reporter

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.14. Arquillian Cube Kubernetes Fabric8

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-fabric8</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

5.2.15. Arquillian Cube Openshift

-
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-openshift</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-
-
-
-

6. Configuration

-
-
-

Arquillian Cube requires some parameters to be configured, some related with Docker server and others related on the image that is being used. -Let’s see valid attributes:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

serverVersion

Version of REST API provided by Docker server. You should check on the Docker site which version of REST API is shipped inside installed Docker service. This field is not mandatory and if it’s not set the default provided version from docker-java will be used.

serverUri

Uri of Docker server. If the Docker server is running natively on Linux then this will be an URI pointing to localhost docker host but if you are using Boot2Docker or a remote Docker server then the URI should be changed to point to the Docker remote URI. It can be a unix socket URI as well in case you are running Docker on Linux (unix:///var/run/docker.sock). If the URI has http:// or https:// scheme, the tlsVerify attribute will be set by Cube to false or true respectively. Also you can read at this section about automatic resolution of serverUri parameter. Also you can use DOCKER_HOST java property or system environment to set this parameter.

dockerRegistry

Sets the location of Docker registry. Default value is the official Docker registry located at https://registry.hub.docker.com

username

Sets the username to connect to Docker registry.

password

Sets the password to connect to Docker registry.

dockerContainers

Each Docker image (or container) can be configured with different parameters. This configuration is provided in YAML format. This property can be used to embed as YAML string value, all configuration.

dockerContainersFile

Instead of embedding YAML as a string, you can set the location of a YAML file with this attribute. The location can be a relative from the root of the project or also a URI that is converted to URL so you can effectively have docker definitions on remote sites.

dockerContainersFiles

You can set a list of locations separated by comma. These locations follow the same rules as dockerContainersFile so it can be a file or an URI. This property can be used to append the definitions from several files.

definitionFormat

Sets the format of content expressed in dockerContainers attribute or in file set in dockerContainersFile. It can contain two possible values CUBE to indicate that content is written following Arquillian Cube format or COMPOSE (default one in case of not set) to indicate that content is written following Docker Compose format.

cubeSpecificProperties

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. This property allows you to add them in already defined containers.

autoStartContainers

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the containers defined as links to this container, so basically Cube resolves all the container dependencies as well e.g. a database where the application saves data, or mail server where application sends an email. That works for things that are DeployableContainer's. In case of defining networks instead of links, Cube will start only all the containers that belongs to a network defined inside definition file. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. The option takes a comma separated list of Docker container ids. e.g. monitor. Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a, or you can add a custom implementation of org.arquillian.cube.spi.AutoStartParser by using reserved word custom: and the full qualified class name.

autoStartOrder

Property to set a new strategy for starting Cubes. Normally the default one is enough but in some cases, if you need to modify it or provide a custom one you can use this property. You need to set the fully qualified name of a class implementing org.arquillian.cube.spi.AutoStartOrder.

tlsVerify

Boolean to set if Cube should connect to Docker server with TLS. This attribute will be ignored if serverUri attribute starts with http:// or https://.

certPath

Path where certificates are stored. If you are not using https protocol this parameter is not required. This parameter accepts starting with ~ as home directory.

boot2dockerPath

Sets the full location (and program name) of boot2docker. For example /opt/boot2dockerhome/boot2docker.

dockerMachinePath

Sets the full location (and program name) of docker-machine. For example /opt/dockermachinehome/docker-machine.

machineName

Sets the machine name in case you are using docker-machine to manage your docker host. This parameter is mandatory when using docker-machine with more than one running machine. In case of having only one docker machine running, it is not necessary to set it since it is auto resolved by cube.

machineDriver

Sets the machine driver in case you are using docker-machine, Cube will create a machine using this driver. This parameter is mandatory when docker-machine is not installed.

dockerMachineCustomPath

Sets the custom location where docker-machine will be downloaded. Default value: ~/.arquillian/machine.

dockerInsideDockerResolution

Boolean to set if Cube should detect that tests are run inside an already started Docker container, so Docker containers started by Cube could be run using DinD (Docker Inside Docker) or DoD (docker On Docker). Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock. By default its value is true. If you want to use an external dockerhost, then you need to set this property to false.

clean

Sometimes you might left some container running inside your docker host with the same name as one defined for Cube test. At these cases Arquillian Cube (actually Docker) complains of a conflict of trying to create a container name that it is already running. If you want that Cube automatically removes these containers you can set this property to true. By default is false.

removeVolumes

Boolean to set if Cube should also remove the volumes associated with a container when removing the container. By default is true. Can be overwritten on container level.

cleanBuildImage

Boolean to set if you set to true all images built by cube are removed and if false no built images are removed. If image is not built by cube it should not be removed. By default is true.

connectionMode

Connection Mode to bypass the Create/Start Cube commands if the a Docker Container with the same name is already running on the target system. This parameter can receive three possible values. STARTANDSTOP which is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown. STARTORCONNECT mode tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution. And last mode is STARTORCONNECTANDLEAVE which is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions. This is a Cube property, not a Docker one, thus it should be inside a <extension qualifier="cube"> tag. See here for an example.

ignoreContainersDefinition

If you set to true then Arquillian Cube will ignore definitions set in dockerContainers, dockerContainersFile and dockerContainersFiles as well as default locations. By default is set to false.

-
-

Some of these properties can be provided by using standard Docker system environment variables so you can set once and use them in your tests too. -Moreover you can set as Java system properties (-D…​) as well.

-
- ---- - - - - - - - - - - - - - - -

serverUri

DOCKER_HOST

certPath

DOCKER_CERT_PATH

machineName

DOCKER_MACHINE_NAME

-
- - - - - -
- - -
-

If dockerContainers, dockerContainersFile or dockerContainersFiles are not set Arquillian Cube automatically search a file named docker-compose.y(a)ml into next places and in following order:

-
-
-
    -
  1. -

    src/{test, main}/docker having more priority test location than `main location.

    -
  2. -
  3. -

    Root directory of project

    -
  4. -
  5. -

    src/distribution

    -
  6. -
  7. -

    src/{test, main}/resources/docker having more priority test location than `main location.

    -
  8. -
  9. -

    src/{test, main}/resources having more priority test location than `main location.

    -
  10. -
-
-
-
-
-

In the next example you can see a whole YAML document with configuration properties. -Keep in mind that almost all of them are configuration parameters provided by Docker remote API. -In this example we are going to explain the attributes that are most used and special cases. -Of course not all of them are mandatory:

-
-
- - - - - -
- - -In YAML adding brackets ("[" "]") is for setting a list. -
-
-
-
-
tomcat: (1)
-  image: tutum/tomcat:7.0 (2)
-  exposedPorts: [8089/tcp] (3)
-  await: (4)
-    strategy: polling (5)
-  workingDir: .
-  alwaysPull: false
-  disableNetwork: true
-  hostName: host
-  portSpecs: [80,81]
-  user: alex
-  tty: true
-  stdinOpen: true
-  stdinOnce: true
-  memoryLimit: 1
-  memorySwap: 1
-  cpuShares: 1
-  cpuQuota: 1
-  shmSize: 64
-  cpuSet: a
-  extraHosts: a
-  attachStdin: true
-  attachStderr: true
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089] (6)
-  cmd: [] (7)
-  dns: [127.0.0.1]
-  volumes: [/tmp]
-  volumesFrom: [tomcat]
-  removeVolumes: true
-  binds:
-    - /host:/container:ro
-  links:
-    - name:alias
-    - name2:alias2
-  portBindings: [8089/tcp, 8081->8080/tcp] (8)
-  privileged: true
-  publishAllPorts: true
-  networkMode: host
-  dnsSearch: [127.0.0.1]
-  entryPoint: [sh]
-  devices:
-    cGroupPermissions: a
-    pathOnHost: b
-    pathInContainer: c
-  restartPolicy:
-    name: failure
-    maximumRetryCount: 1
-  capAdd: [a]
-  capDrop: [b]
-  extends: container-id (9)
-  manual: true (10)
-  killContainer: true  (11)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1The name that are going to be assign to running container. It is mandatory.
2The name of the image to be used. It is mandatory. If the image has not already been pulled by the Docker server, Arquillian Cube will pull it for you. If you want to always pull latest image before container is created, you can configure alwaysPull: true.
3Sets exposed ports of the running container. It should follow the format port number slash(/) and _protocol (udp or tcp). Note that it is a list and it is not mandatory.
4After a container is started, it starts booting up the defined services/commands. Depending on the nature of service, the lifecycle of these services are linked to start up or not. For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the client, the container never finishes to start. But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready. It is not mandatory and by default polling with ss command strategy is used.
5In strategy you set which strategy you want to follow. Currently three strategies are supported. static, native and polling.
6You can pass environment variables by using env. In this section you can set special dockerServerIp string which at runtime will be replaced by Cube to current docker server ip.
7After the container is up, a list of commands can be executed within it.
8Port forwarding is configured using portBinding section. It contains a list of exposedPort and port separated by arrow (). If only one port is provided, Arquillian Cube will expose the same port number. In this example the exposed port 8089 is mapped to 8089 and exposed port 8080 is mapped to 8081.
9You can extend another configuration. Any top level element and it’s children from the target container-id will be copied over to this configuration, unless they have been defined here already.
10You can use manual to indicate that this container is going to be started or in the test manually using CubeController or started by an extension. This attribute is ingorned in case of arquillian containers (none autostart containers) or in case of a linked container that comes from a none manual container.
11Kills the container instead of stopping it normally. By default is false so containers are stopped.
-
-
-

As we’ve seen in the basic example the definition of the Arquillian Cube scenarios are described in dockerContainers property. -But if you want you can avoid using this property by simply creating a file called cube in the root of the classpath of your project. -Arquillian Cube will read it as if it was defined in arquilllian.xml file.

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-  env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-  portBindings: [8089/tcp, 8080/tcp]
-
-
-
-

6.1. Networks

-
-

You can define networks using cube format (Docker Compose v1 (the one supported by Cube) does not have support to networks. -For defining them you only need to use the reserved word networks.

-
-
-
src/test/resources/cube
-
-
networks: (1)
-   mynetwork: (2)
-       driver: bridge (3)
-tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork  (4)
-
-
-
- - - - - - - - - - - - - - - - - -
1Reserved word to indicate start of network definition
2Network name
3Driver used. Currently this is the only supported property due not support in docker-java
4Sets the network where container must join.
-
-
-

You can also use networks to connect to more than one network:

-
-
-
src/test/resources/cube
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  networkMode: mynetwork
-  networks:
-    - mynetwork
-    - myothernetwork
-
-
-
-
-

6.2. cube.environment

-
-

In case you use the dockerContainersFile to configure a YAML file (Cube format or Compose format) for loading the definition of your containers, you can use cube.environment system property to append a prefix to the file you want to load.

-
-
-

For example, in case you set dockerContainersFile to docker-compose.yml and cube.environment not set, Cube loads a file named docker-compose.yml. But if cube.environment is set to qa (-Dcube.environment=qa), the Cube loads a file called docker-compose.qa.yml.

-
-
-

This property is useful for loading different configurations depending on the environment you are running the tests.

-
-
-
-

6.3. Await

-
-

After a container is started, it starts booting up the defined services/commands. -Depending on the nature of service, the lifecycle of these services are linked to start up or not. -For example Tomcat, Wildlfy, TomEE and in general all Java servers must be started in foreground and this means that from the point of view of the Docker client, the container never finishes to start. -But on the other side other services like Redis are started in background and when the container is started you can be sure that Redis server is there. -To avoid executing tests before the services are ready, you can set which await strategy should be used from Arquillian Cube side to accept that Docker container and all its defined services are up and ready.

-
-
-

Currently next await strategies are supported:

-
-
-
-
native
-
-

it uses wait command. In this case current thread is waiting until the Docker server notifies that has started. In case of foreground services this is not the approach to be used.

-
-
polling
-
-

in this case a polling (with ping or ss command) is executed for 5 seconds against all exposed ports. When communication to all exposed ports is acknowledged, the container is considered to be up. This approach is the one to be used in case of services started in foreground. By default polling executes ss command inside the running container to know if the server is already running. -Also you can use a ping strategy from client by setting type attribute to ping; Note that ping only works if you are running Docker daemon on localhost. -You can also use wait-for-it script which is automatically downloaded, copied inside container and executed inside it. To do it you need to set type property to waitforit. In almost all cases the default behaviour matches all scenarios. If it is not specified, this is the default strategy. -By default if you use ss strategy but ss command is not installed into the container it fallsback automatically to waitforit strategy.

-
-
static
-
-

similar to polling but it uses the host ip and specified list of ports provided as configuration parameter. This can be used in case of using Boot2Docker.

-
-
sleeping
-
-

sleeps current thread for the specified amount of time. You can specify the time in seconds or milliseconds.

-
-
log
-
-

it looking for a specified pattern in container log to detect service startup. This can be used when there is no port to connect or connecting to the port successfully doesn’t mean the service is fully initialized.

-
-
http
-
-

polls through a configured http endpoint checking for http response code and optionally the answer content or headers.

-
-
<fullyqualifiedclassname>
-
-

if you specify a fully qualified class name, Arquillian Cube will instantiate the given class. In this way you can implement your own await strategies. There are two rules to follow, the first one is that class must implement AwaitStrategy and the second one is that one default constructor must be provided. Optionally you can add fields/setters for types Cube, DockerClientExecutor or Await to inject them into the await strategy.

-
-
-
-
-

By default in case you don’t specify any await strategy, polling with ss command is used with automatic fallback to wait-fo_it strategy.

-
-
-
Example native
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: native
-
-
-
-
Example polling using ss command by default
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: polling
-    sleepPollingTime: 200 s (1)
-    iterations: 3  (2)
-
-
-
- - - - - - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
2Optional parameter to configure number of retries to be done. By default 10 iterations are done.
-
-
-
Example static
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: static
-    ip: localhost
-    ports: [8080, 8089]
-
-
-
-
Example sleeping
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: sleeping
-    sleepTime: 200 s  (1)
-
-
-
- - - - - -
1Optional parameter to configure sleeping time between poling. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
-
-
-
Example log
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: log
-    match: 'Server startup' (1)
-    stdOut: true (2)
-    stdErr: true (3)
-    timeout: 15 (4)
-    occurrences: 2  (5)
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1Mandatory parameter to configure the pattern that signals the service started. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to enable scanning of standard output log. Default is true.
3Optional parameter to enable scanning of standard error log. Default is false.
4Optional parameter to configure timeout. It is expressed in seconds and by default is 15.
5Optional parameter to configure the number of times that it should match. By default is 1.
-
-
-
Example http
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: http
-    match: 'Server startup' (1)
-    responseCode: 201 (2)
-    url: http://dockerHost:8080 (3)
-    sleepPollingTime: 200 s (4)
-    iterations: 3 (5)
-    headers:
-        X-Cube: Docker  (6)
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Parameter to configure the pattern that signals the service returned correctly value. To use regular expression just prefix the pattern with regexp:.
2Optional parameter to set which response http code is the expected one from service. Default is 200.
3Mandatory parameter that sets the url where to connect. dockerHost is substituted by Cube to Docker Host.
4Optional parameter to configure sleeping time between each call in case of fail. You can set in seconds using s or miliseconds using ms. By default time unit is miliseconds and value 500.
5Optional parameter to configure number of retries to be done. By default 10 iterations are done.
6Optional parameter to check header’s value returned by service.
-
-
-

Custom Await strategy:

-
-
-
org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
public class CustomAwaitStrategyImpl implements AwaitStrategy {
-
-  Await params;
-  DockerClientExecutor dockerClientExecutor;
-  Cube<?> cube;
-
-  public void setCube(Cube<?> cube) {
-    this.cube = cube;
-  }
-
-  public void setDockerClientExecutor(DockerClientExecutor dockerClientExecutor) {
-    this.dockerClientExecutor = dockerClientExecutor;
-  }
-
-  public void setParams(Await params) {
-     this.params = params;
-  }
-
-  @Override
-  public boolean await() {
-    return this.params != null && this.dockerClientExecutor != null && this.cube != null;
-  }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  exposedPorts: [8089/tcp]
-  await:
-    strategy: org.arquillian.cube.docker.impl.await.CustomAwaitStrategyImpl
-
-
-
-

6.3.1. @HealthCheck annotation

-
-

Sometimes default await strategy because when the service opens the port it does not mean that the service is up and running. -For example in case of Tomcat, exposed port is opened when the application is deployed so default strategy works perfectly, but in case of Wildfly the port is opened when the server is up and running but not when the application has been deployed.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @HealthCheck annotation.

-
-
-

By default annotating your test class with it, next default parameters are used:

-
-
-
-
context: /
-schema: http
-port: 8080
-method: GET
-containerName: null
-responseCode: 200
-iterations: 40
-interval: 500ms
-timeout: 2s
-
-
-
-

Each of these attributes are configurable with custom values.

-
-
- - - - - -
- - -Time attributes like timeout and interval uses docker-compose durations approach of using timespan format such as 1m30s. -
-
-
- - - - - -
- - -If containerName is set to null port attribute is used, otherwise port is considered an exposed port and it is resolved against the given container. -
-
-
-
-

6.3.2. @Sleep annotation

-
-

Sometimes you need to sleep your execution for some specific amount of time and you have no way to do it using an http health check. -In this situations a sleep might do the work.

-
-
-

To avoid this problem and continue using default await strategy you can annotate your test class with @Sleep annotation which receives as value an string that represents a timespan.

-
-
-

By default the time specified is in milliseconds so annotating the test class with @Sleep("1000") makes your test class sleeps 1 second before executing all test methods. -You can also use the timespan format and write something like @Sleep("1m30s") which makes your test class sleeps for one minute and a half before executing all test methods.

-
-
-
-
-

6.4. Inferring exposedPorts from portBinding

-
-

When you are using Docker you can set two different parameters, exposedPort and portBinding. -exposedPorts are ports that are available inside Docker infrastructure and they are used for communication between Docker containers but not from outside. -On the other side portBindings are a way to instruct Docker container to publish a port to be available from outside (for example from our test).

-
-
-

It seems reasonable that if you set a port binding this port should automatically be exposed port as well. -For this reason in Arquillian Cube you can use portBinding and it will automatically set to exposed port.

-
-
-

In next example we are only setting portBinding and Arquillian Cube will instruct Docker to expose port 8080 and of course bind the port 8080 so it can be accessible from outside.

-
-
-
arquillian.xml
-
-
daytime:
-  buildImage:
-    dockerfileLocation: src/test/resources/undertow
-    noCache: true
-    remove: true
-  await:
-    strategy: polling
-  portBindings: [8080/tcp]
-
-
-
-

Also it is not necessary to set the network protocol (tcp or udp). -If protocol is not specified portBindings: ["8080"] then tcp is used by default. -Notice that you need to add double-quotes to stringify the value.

-
-
-
-

6.5. Allow connecting to a running container

-
-

With the configuration option connectionMode you can bypass -the Create/Start Cube commands if the a Docker Container with the same name is already -running on the target system. If so, Arquillian Cube will reuse that Container moving forward.

-
-
-

This allows you to prestart the containers manually during development and just -connect to them to avoid the extra cost of starting the Docker Containers for each test -run. This assumes you are not changing the actual definition of the Docker Container itself.

-
-
-

An example of this configuration could be:

-
-
-
arquillian.xml
-
-
<extension qualifier="cube">
-  <property name="connectionMode">STARTORCONNECT</property>
-</extension>
-
-<extension qualifier="docker">
-  <property name="serverVersion">1.12</property>
-  <property name="serverUri">http://localhost:2375</property>
-  <property name="dockerContainers">
-      tomcat:
-        #more properties
-  </property>
-</extension>
-
-
-
-

connectionMode is an attribute that can receive three parameters:

-
-
-
-
STARTANDSTOP
-
-

it is the default one if not set any and simply creates and stops all Docker Containers. If a container is already running, an exception is thrown.

-
-
STARTORCONNECT
-
-

it tries to bypass the Create/Start Cube commands if a container with the same name is already running, and if it is the case doesn’t stop it at the end. But if container is not already running, Cube will start one and stop it at the end of the execution.

-
-
STARTORCONNECTANDLEAVE
-
-

it is exactly the same of STARTORCONNECT but if container is started by Cube it won’t be stopped at the end of the execution so it can be reused in next executions.

-
-
-
-
-
-

6.6. Before Stop Events

-
-

Sometimes when the tests has finished and container is stopped you want to inspect some data like container console or getting a file from the container to manual inspecting. -In these cases you can configure each container to copy console log or copy a file/s from container to local machine just before container is stopped. -Or if your need is more specific, you can provide a custom implementation

-
-
-

Next snippet shows how to copy a directory from container to local disk:

-
-
-
-
tomcat_default:
-  image: tutum/tomcat:7.0
-  beforeStop: (1)
-    - copy: (2)
-        from: /test
-        to: /tmp
-
-    - log: (3)
-        to: /tmp/container.log
-
-    - customBeforeStopAction: (4)
-        strategy: <fullyqualifiedclassname>
-
-
-
- - - - - - - - - - - - - - - - - -
1beforeStop goes into the container section and may contain a list of copy and log elements.
2copy is used to notify that we want to copy some directories or files form from container location to to local location.
3log is used to notify that we want to copy container log to to local location.
4customBeforeStopAction is used to notify that we provide our own implementation of a stop action. There are -two rules to follow. The first is that the class must implement 'BeforeStopAction' and the second one is that the class must have a default constructor. -Optionally fields/setters for types 'DockerClientExecutor' and 'CubeId' for containerId can be used to inject them into -the custom beforeStop action
-
-
-

In case of log command the standard output and the error output are returned. -log Docker command can receive some configuration paramters and you can set them too in configuration file.

-
-
-
Example of log parameters
-
-
beforeStop:
-  - log:
-    to: /tmp/container.log
-    follow: true
-    stdout: true
-    stderr: false
-    timestamps: true
-    tail: 10
-
-
-
-

Custom BeforeStop action:

-
-
-
-
package org.arquillian.cube.docker.impl.beforeStop;
-
-import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
-import org.arquillian.cube.impl.model.CubeId;
-import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
-
-public class CustomBeforeStopActionImpl implements BeforeStopAction {
-
-    private DockerClientExecutor dockerClientExecutor;
-    private CubeId containerID;
-
-    @Override
-    public void doBeforeStop() {
-
-    }
-}
-
-
-
-

and the configuration comes as:

-
-
-
Example.yml
-
-
tomcat:
-  image: tutum/tomcat:7.0
-  beforeStop:
-    - customBeforeStopAction:
-        strategy: org.arquillian.cube.docker.impl.beforeStop.CustomBeforeStopActionImpl
-
-
-
-
-

6.7. CubeSpecificProperties

-
-

In case you are using COMPOSE format, you cannot configure cube custom properties like await or beforeStop. -You can use a property called cubeSpecificProperties allows you to add them in already defined containers.

-
-
- - - - - -
- - -This property only overrides custom properties defined by CUBE format. -
-
-
-
arquillian.xml
-
-
<property name="cubeSpecificProperties">
-    tomcat:
-      removeVolumes: true
-      await:
-        strategy: polling
-      beforeStop:
-        - copy:
-            from: /test
-            to: /tmp";
-</property>
-
-
-
-

Previous snippet would override await strategy to polling and set a beforeStop event in cube container named tomcat defined in a previous docker-compose definition.

-
-
-
-

6.8. Automatic serverUri resolution

-
-

serverUri parameter is where you configure the Uri of Docker server. -This parameter is not mandatory and in case you don’t set it, Arquillian Cube will use next values:

-
- ---- - - - - - - - - - - - - - - - - - - -

Linux

unix:///var/run/docker.sock

Windows

tcp://dockerHost:2376

MacOS

tcp://dockerHost:2376

Docker Machine

tcp://dockerHost:2376

-
-
-
-
-

7. Boot2Docker and Docker Machine

-
-
-

If you are using boot2docker or docker machine there are some parameters that depends on the local installation. -For example boot2docker ip is not localhost and may change every time you start a new boot2docker instance. -Also every time you start boot2docker copies required certificates to home directory of local machine.

-
-
-

Arquillian Cube offers some automatic mechanisms to use boot2docker or docker machine in Cube.

-
-
-

The first one is that serverUri parameter can contain the word dockerHost like for example https://dockerHost:2376. -When Cube is started it will check if the serverUri contains the dockerHost word, and if it is the case it will do next things:

-
-
-
    -
  1. -

    if docker machine name is provided by using machineName property then Cube resolves if Docker Machine is installed, otherwise machineDriver property must be provided to create the machine.

    -
  2. -
  3. -

    if docker machine name is provided by using machineName property then Docker Machine command is run to get the ip to be replaced in dockerHost.

    -
  4. -
  5. -

    if previous conditions are not met, then boot2docker command is run to get the ip to be replaced in dockerHost.

    -
  6. -
-
-
- - - - - -
- - -In case of using docker machine with only one machine running, it is not necessary to use machineName property since Cube will be able to resolve it automatically. -
-
-
-

7.1. Boot2Docker

-
-

In case of boot2docker it will run the command boot2docker ip to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that boot2docker command is on PATH, but you can configure its location by using boot2dockerPath property which is the full location (and program name) of boot2docker. -For example /opt/boot2dockerhome/boot2docker.

-
-
-

boot2docker runs in https and you need to set the certificates path. -These certificates are copied by boot2docker by default at <HOME>/.boot2docker/certs/boot2docker-vm. -If this property is not set and the serverUri contains dockerHost, then this property is automatically configured to <HOME>/.boot2docker/certs/boot2docker-vm so you don’t need to worry to set for each environment.

-
-
-
-

7.2. Docker Machine

-
-

In case of docker-machine it will run the command docker-machine ip <machineName> to get the ip and substitute the dockerHost keyword to the ip returned by that command.

-
-
-

Note that by default Arquillian Cube assumes that docker-machine command is on PATH, but you can configure its location by using the dockerMachinePath property which is the full location (and program name too) of docker-machine. -For example /usr/bin/docker-machine. But, if docker-machine command is not on PATH, Cube will download the latest version. In this case, machineDriver must be provided to create the machine.

-
-
-

docker-machine can run with boot2docker together. -And this docker host instance runs in https so you need to set the certificates path. -These certificates are copied by docker-machine by default at <HOME>/.docker/machine/machines. -If this property is not set and docker-machine is run, then this property is automatically configured to default location, so you don’t need to worry to set for each environment.

-
-
-

For example you can configure arquillian.xml file to use docker-machine as:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="serverVersion">${docker.api.version}</property>
-    <property name="definitionFormat">COMPOSE</property>
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1Sets docker machine to dev.
-
-
-

Notice that you only need to add machineName property in case you have more than one machine running, everything else it is exactly the same as previous examples.

-
-
-
-
-
-

8. Building containers

-
-
-

To build a container Docker uses a file called Dockerfile http://docs.docker.com/reference/builder/. -Arquillian Cube also supports building and running a container from a Dockerfile.

-
-
-

To set that Arquillian Cube must build the container, the image property must be changed to buildImage and add the location of Dockerfile.

-
-
-

Let’s see previous example but instead of creating a container from a predefined image, we are going to build one:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  tomcat:
-    buildImage: (1)
-      dockerfileLocation: src/test/resources-tomcat-7-dockerfile/tomcat (2)
-      noCache: true (3)
-      remove: true (4)
-      dockerfileName: my-dockerfile (5)
-    await:
-      strategy: polling
-    env: [JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-    portBindings: [8089/tcp, 8080/tcp]
-</property>
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
1buildImage section is used in front of image. In case of both sections present in a document, image section has preference over buildImage.
2dockerfileLocation contains the location of Dockerfile and all files required to build the container.
3Property to enable or disable the no cache attribute.
4Property to enable or disable the remove attribute.
5Property to set the dockerfile name to be used instead of the default ones.
-
-
- - - - - -
- - -dockerfileLocation can be a directory that must contains Dockerfile in root directory (in case you don’t set dockerfileName property), also a tar.gz file or a URL pointing to a tar.gz file. -
-
-
-

An example of Dockerfile is:

-
-
-
src/test/resources-tomcat-7-dockerfile/tomcat/Dockerfile
-
-
FROM tutum/tomcat:7.0
-
-ENV JAVA_OPTS -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-ADD tomcat-users.xml /tomcat/conf/ (1)
-EXPOSE 8089
-CMD ["/tomcat/bin/catalina.sh","run"]
-
-
-
- - - - - -
1tomcat-users.xml file is located at same directory as Dockerfile.
-
-
-
-
-

9. Docker-Compose Format

-
-
-

Instead of using Arquillian Cube format, you can use Docker Compose format to define containers layout. This means that you can use the same Docker Compose file for running your tests with Arquillian Cube and without any change run docker-compose up command from terminal and get the same result.

-
-
-

It is important to note that this is not a docker-compose implementation but only the docker-compose format. This means that for example you cannot execute some CLI commands of docker-compose like start several instances of same service.

-
-
-

In case of some specific Arquillian Cube attributes like await strategy cannot be configured and the default values are going to be used.

-
-
-

Moreover there are some docker-compose commands that are not implemented yet due to restrictions on docker-java library. These commands are pid, log_driver and security_opt. But they will be implemented as soon as docker-java library adds their support.

-
-
-

Last thing, in case you define a command that is not implemented in Arquillian Cube, this command will be ignored (no exception will be thrown), but a log line will be printed notifying this situation. Please it is really important that if this happens you open a bug so we can add support for them. Althought this warning we will try to maintain aligned with the latest docker-compose format.

-
-
-

Let’s see how you can rewrite previous HelloWorld example with Tomcat to be used using docker-compose format.

-
-
-

First let’s create a file called envs on root of the project which configures environment variables:

-
-
-
envs
-
-
TOMCAT_PASS=mypass
-JAVA_OPTS=-Djava.rmi.server.hostname=dockerServerIp -Dcom.sun.management.jmxremote.rmi.port=8088 -Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-
-
-
- - - - - -
- - -You can use dockerServerIp as special tag for identify the docker host IP. Before injecting the environement variables, Cube will change it to real host IP. -
-
-
-

Then you can create a file called docker-compose.yml following docker-compose commands on root of the project:

-
-
-
docker-compose.yml
-
-
tomcat:
-  env_file: envs
-  image: tutum/tomcat:7.0
-  ports:
-      - "8089:8089"
-      - "8088:8088"
-      - "8081:8080"
-
-
-
-

and finally you can configure in arquillian.xml file that you want to use docker-compose format.

-
-
- - - - - -
- - -
-

In case you use context with a GIT repository, you should add the following dependency org.eclipse.jgit:org.eclipse.jgit:${jgit.version}. -Due of library conflicts between docker-java and jgit, that they are sharing common libraries but different unsupported versions, you need to exclude and include some of them.

-
-
-

For example in case of using Maven, your pom.xml should look like:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.eclipse.jgit</groupId>
-  <artifactId>org.eclipse.jgit</artifactId>
-  <scope>test</scope>
-  <exclusions>
-    <exclusion>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </exclusion>
-  </exclusions>
-</dependency>
-<dependency>
-  <groupId>org.slf4j</groupId>
-  <artifactId>slf4j-api</artifactId>
-  <version>1.7.21</version>
-  <scope>test</scope>
-</dependency>
-
-
-
-
-
-
src/test/resources/arquillian.xml
-
-
<extension qualifier="docker">
-  <property name="serverVersion">1.13</property>
-  <property name="serverUri">localhost</property>
-  <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-<container qualifier="tomcat">
-  <configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Notice that you don’t need to specify definitionFormat since docker compose format is the default one.

-
-
-

And that’s all, you can now reuse your existing docker-compose files in Arquillian Cube too. -You can see the full example at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-docker-compose

-
-
-
-
-

10. Enrichers

-
-
-

Arquillian Cube comes with a few enrichers.

-
-
-

One for injecting the CubeID(containerId) of the current container created for executing the test, one that injects the CubeController to call lifecycle methods on any cube and one that injects com.github.dockerjava.api.DockerClient instance used to communicate with Docker server. Also you can inject in your tests the Docker Host IP used for running containers by using @HostIp annotation.

-
-
-

DockerClient, Docker Host IP, Docker Host Port and Cube Ip injection only work if the tests are run in client mode, that is by using @RunAsClient or by setting the testable property to false @Deployment(testable = false). -Also they work if you run Arquillian in standalone mode.

-
-
-

These can be injected using the @ArquillianResource annotation.

-
-
-

As examples:

-
-
-
CubeIDResourceProvider.java
-
-
@ArquillianResource
-CubeID containerId;
-
-
-
-
CubeResourceProvider.java
-
-
@ArquillianResource
-DockerClient dockerClient;
-
-
-
-
CubeControllerProvider.java
-
-
@ArquillianResource
-CubeController cubeController;
-
-
-
-
DockerHostProvider.java
-
-
@HostIp
-String ip;
-
-
-
-
DockerHostPortProvider.java
-
-
@HostPort(containerName = "tomcat", value = 8080)
-int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat.
-
-
-
-
CubeIpProvider.java
-
-
@CubeIp(containerName = "tomcat")
-String ip;
-
-
-
-

When running Arquillian Cube with Standalone you can enrich the test with URL.

-
-
-
DockerUrlProvider.java
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080) // resolves bind port
-@ArquillianResource
-private URL url;
-
-
-
-

When running Arquillian Cube against a OpenShift’s pod you can retrieve the route to the pod’s service just by using the example code below:

-
-
-
RouterURLEnricher.java
-
-
@RouteURL("app-name")
-private URL url;
-
-
-
-

If the route is not resolvable, you need to set the routerHost setting to the IP address of the OpenShift router. -You can configure it in arquillian.xml:

-
-
-
arquillian.xml
-
-
 <extension qualifier="openshift">
-        ....
-        <property name="routerHost">192.168.10.10</property>
-        ...
-    </extension>
-
-
-
-

Or just by setting the system property openshift.router.host. -To obtain the router address from your OpenShift instance you can execute the command below: -oc describe svc/router -And look for the Endpoints field.

-
- -
-

10.1. Docker Inside Docker / Docker On Docker

-
-

If you are running your tests inside your continuous integration/delivery server (for example in Jenkins) and at the same time the server is running inside Docker. Then the docker containers started for Cube are run inside a Docker container. -So you effectively face the Docker inside Docker problem.

-
-
-

From Arquillian Cube perspective we cannot do a lot of things, more then adapting to this situation by changing the serverUri. -Basically it ignores any SERVER_URI, Boot2Docker or docker-machine properties and sets the serverUri to unix:///var/run/docker.sock.

-
-
-

You can avoid this behaviour by setting dockerInsideDockerResolution to false.

-
-
-

INFO: In this case almost all work should be done in infrastructure level by configuring correctly Docker instances. For this reason it is important that before running Cube tests, you test manually your infrastructure to be sure that everything is connected as expected.

-
-
-

In next sections, some minor information is provided on how to run Docker inside/on Docker. Keep in mind that you need to configure continuous integration/delivery correctly.

-
-
-

10.1.1. Docker Inside Docker

-
-

You can find more information about Docker Inside Docker at: https://github.com/jpetazzo/dind

-
-
-

Also if you are using Jenkins you can use next Jenkins Slave. kmadel/dind-jenkins-slave:1.4 running with privileged flag.

-
-
-
-

10.1.2. Docker On Docker

-
-

If instead of running Docker inside Docker, you want to use the Docker instance/host of the "parent" Docker, you can map as volume the Docker CLI, Docker socket and apparmor library from parent to child container.

-
-
-

-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/lib/x86_64-linux-gnu/libapparmor.so.1.1.0:/usr/lib/x86_64-linux-gnu/libapparmor.so.1

-
-
-
-
-

10.2. CubeController

-
-

CubeController is facade class that let’s you interact with any cube container. - It offers some operations like creating and destroying Cubes (in case of Docker, it is Docker containers), copy a directory to local directory, get a log with all the changes that happened to Cube filesystem, execute a Top command or copy the logs to local file.

-
-
-

Suppose you have next Docker Container definition in dockerContainers property:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    manual_database:
-        image: zhilvis/h2-db
-        portBindings: [1521/tcp, 8181->81/tcp]
-</property>
-
-
-
-

If you enrich your test with CubeController then you will be able to:

-
-
-
    -
  • -

    call cubeController.create("manual_database") to create the Cube defined in dockerContainers with name manual_database.

    -
  • -
  • -

    call cubeController.start("manual_database") to start the given Cube.

    -
  • -
  • -

    call cubeController.stop("manual_database") to stop the given Cube.

    -
  • -
  • -

    call cubeController.destroy("manual_database") to destroy the given Cube.

    -
  • -
-
-
-

But also it offers some extra operations not related with the lifecycle of a Cube.

-
-
-
    -
  • -

    cubeController.copyFileDirectoryFromContainer("manual_database", "/db", newFolder.getAbsolutePath()) to copy content from container folder /db to newFolder local location.

    -
  • -
  • -

    List<ChangeLog> changesOnFilesystem = cubeController.changesOnFilesystem("manual_database") to returns a log with all changes that has occurred inside given Cube.

    -
  • -
  • -

    TopContainer top = cubeController.top("manual_testing") to get the result of executing top command inside Cube.

    -
  • -
  • -

    cubeController.copyLog("manual_testing", follow, stdout, stderr, timestamp, tail, byteArrayOutputStream) to copy Cube log to given outputStream. This operation only works in Client mode.

    -
  • -
-
-
-
-

10.3. Auto starting Cubes outside of Arquillian Containers

-
-

Probably any application you may write will need an application/servlet container but also other servers like database server or mail server. -Each one will be placed on one Docker Container. -So for example a full application may contain one Docker Container with an application server (for example Wildfly) and another container with a database (for example H2).

-
-
-

Arquillian Cube can orchestrate these containers as well.

-
-
-

An example of orchestration can be:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-  wildfly_database:
-    extends: wildfly
-    links:
-      - database:database (1)
-  database:
-    image: zhilvis/h2-db
-    exposedPorts: [81/tcp, 1521/tcp]
-    await:
-      strategy: polling
-    portBindings: [1521/tcp, 8181->81/tcp]
-  </property>
-
-<container qualifier="wildfly_database">
-  <configuration>
-    <property name="target">wildfly:8.1.0.Final:remote</property>
-    <property name="username">admin</property>
-    <property name="password">Admin#70365</property>
-  </configuration>
-</container>
-
-
-
- - - - - -
1We use link property to connect Wildfly container to database container.
-
-
-

In this case when a test is started both containers are started and when both are ready to receive requests, the test will be executed.

-
-
-

And the database definition shall be:

-
-
-
UserRepository.java
-
-
@DataSourceDefinition(
-  name = "java:app/TestDataSource",
-  className = "org.h2.jdbcx.JdbcDataSource",
-  url = "jdbc:h2:tcp://database:1521/opt/h2-data/test",
-  user = "sa",
-  password = "sa"
-)
-@Stateless
-public class UserRepository {
-
-    @PersistenceContext
-    private EntityManager em;
-
-    public void store(User user) {
-        em.persist(user);
-    }
-}
-
-
-
-

Cube will normally start a Docker container when it has the same name as an active Arquillian container and all the links from this container to another containers as we have seen in previous example. Basically Cube resolves all the container depdendencies as well e.g. a database where the application saves data, or mail server where application sends an email.

-
-
-

That works for things that are DeployableContainer’s. For any other container services that might not have a link to the DeployableContainer, e.g. a monitor, you can use the autoStartContainers option to define which Docker containers to automatically start up. -The option takes a comma separated list of Docker container ids. e.g. monitor. -Arquillian Cube will attempt to start the containers in parallel if possible as well as start any linked containers. -Also if you need to start several images, instead of adding them as CSV, you can use a regular expression by prefixing with regexp:, for example setting the property to regexp:a(.*) would start all container ids starting with a.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">regexp:a(.*)</property>
-</extension>
-
-
-
-

Also you can provide your own implementation of autostart containers. -To make it so, first you need to implement org.arquillian.cube.spi.AutoStartParser interface.

-
-
-
-
public class ChangeNameAutoStartParser implements AutoStartParser { (1)
-
-    @Inject (2)
-    public Instance<CubeDockerConfiguration> cubeDockerConfigurationInstance;
-
-    @Override
-    public Map<String, Node> parse() { (3)
-        final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent();
-
-        final Map<String, Node> nodes = new HashMap<>();
-        final Set<String> containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet());
-
-        for (String name : containersNames) {
-            nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name));
-        }
-
-        return nodes;
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1Need to implement AutoStartParser interface
2You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry
3Returns a map with the name of the map and id.
-
-
-

Then you need to use reserved word custom: + full qualified class name in the autoStartContainers property.

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="autoStartContainers">custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser</property>
-</extension>
-
-
-
-
-

10.4. Auto-Remapping

-
-

Arquillian Cube can automatically configure default ports of container in case of port forwarding.

-
-
-

What Arquillian Cube does internally is remapping default DeployableContainer port values to the ones configured in Docker Containers configuration.

-
-
-

Suppose you have a Docker Container configuration like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_default:
-      image: tutum/tomcat:7.0
-      exposedPorts: [8089/tcp]
-      await:
-        strategy: polling
-      env: [TOMCAT_PASS=mypass, JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8089 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false]
-      portBindings: [8089/tcp, 8081->8080/tcp] (1)
-</property>
-
-
-
- - - - - -
1Note that the exposed port is the 8081.
-
-
-

Then in theory you should configure the remote Tomcat adapter to port 8081 on your arquillian.xml file. -But let’s say that you are using that remote adapter for a remote local machine Tomcat (outside Docker) too, and is configured to use 8080 port.

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">localhost</property>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

Which basically uses default port (8080) to connect to remote server.

-
-
-

In this case you don’t need to create a new container tag, Arquillian Cube is smart enough to change the default port value automatically; in case of Tomcat 8080 to 8081. -Arquillan Cube will apply autoremapping to all properties that contains port as a substring of the property, and will remap if it is necessary.

-
-
- - - - - -
- - -Automapping only works in case you want to change the default server port to a Docker port forwarded port. -
-
-
-
-

10.5. DockerServerIp and Containers

-
-

If you are using a remote docker server (not on localhost) or for example boot2docker you may want to set that ip to Arquillian remote adapter configuration so it can deploy the archive under test. -In these cases you can hardcode this ip to Arquillian container adapter configuration or you can use the special tag dockerServerIp. -At runtime these tag will be replaced by Arquillian Cube to docker server ip configured in serverUri parameter. -This replacement only works in properties that contains the string host or address in property name.

-
-
-

So for example:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-        <property name="serverUri">http://192.168.0.2:2756</property> (1)
-  ...
-</extension>
-<container qualifier="tomcat_default">
-  configuration>
-    <property name="host">dockerServerIp</property> (2)
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
- - - - - - - - - -
1We set the serverUri as usually.
2dockerServerIp is replaced at runtime.
-
-
-

The host property will be replaced automatically to 192.168.0.2.

-
-
- - - - - -
- - -This also works in case you set serverUri using boot2docker special word or by using the defaults. Read more about it Boot2Docker section and Automatic serverUri resolution section. -
-
-
-

In case of using unix socket dockerServerUri is replaced to localhost.

-
-
-

Also Arquillian Cube can help you in another way inferring boot2docker ip. -In case you are running in MACOS or Windows with boot2docker, you may not need to set host property at all nor using dockerServerIp. -Arquillian Cube will inspect any property in configuration class that contains the word address or host that it is not overriden in arquillian.xml and it will set the boot2docker server automatically.

-
-
-

So previous example could be modified to:

-
-
-
arquillian.xml
-
-
<container qualifier="tomcat_default">
-  configuration>
-    <property name="user">admin</property>
-    <property name="pass">mypass</property>
-  </configuration>
-</container>
-
-
-
-

And in case you are running on Windows or MacOS, `host`property will be automatically set to the +boot2docker +ip.

-
-
-
-

10.6. System Properties Injection

-
-

Arquillian Cube sets some system properties to be used in tests in case you cannot use enrichers (i.e in case of JUnit rules). -These system properties are:

-
-
-
    -
  • -

    arq.cube.docker.host with Docker Host host.

    -
  • -
-
-
-

For each container started:

-
-
-
    -
  • -

    arq.cube.docker.<cubeid>.ip with container ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.internal.ip with container internal ip.

    -
  • -
  • -

    arq.cube.docker.<cubeid>.port.<exposedport> with binding port of giving exposed port.

    -
  • -
-
-
-
-
-
-

11. Parallel Execution

-
-
-

Sometimes using any Mave/Gradle/Jenkins plugin you end up by executing tests in parallel. -This means that same docker-compose file is executed for all tests. -The problem is that probably docker host is the same. -So when you start the second test (in parallel) you will get a failure regarding that a container with same name is already started in that docker host.

-
-
-

So arrived at this point you can do two things:

-
-
-
    -
  • -

    You can have one docker host for each parallel test and override serverUri property at each case using system property (arq.extension.docker.serverUri).

    -
  • -
  • -

    You can reuse the same docker host and use star operator.

    -
  • -
-
-
-

11.1. Star Operator

-
-

Star operator let’s you indicate to Arquillian Cube that you want to generate cube names randomly and adapt links as well. -In this way when you execute your tests in parallel there will be no conflicts because of names or binding ports.

-
-
-

Let’s see an example:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat*:
-      image: tutum/tomcat:8.0
-      portBindings: [8080/tcp]
-      links:
-        - ping*
-    ping*:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

You add the special character * to indicate that cube name should be randomized.

-
-
-

With previous example Cube will:

-
-
-
    -
  1. -

    Generate a unique UUID.

    -
  2. -
  3. -

    Substitute cube name and links using the name + UUID

    -
  4. -
  5. -

    In case of using an alias, don’t add the * since it is extended from the service name

    -
  6. -
  7. -

    Bind port is going to be changed to a random private port (49152 - 65535)

    -
  8. -
  9. -

    Add an environment variable with new hostname. This environment variable is <name>_HOSTNAME

    -
  10. -
-
-
-

So for example the result file could look like:

-
-
-
arquillian.xml
-
-
<property name="dockerContainers">
-    tomcat_123456:
-      image: tutum/tomcat:8.0
-      portBindings: [54678 -> 8080/tcp]
-      env: [ping_HOSTNAME=ping_123456]
-      links:
-        - ping_123456
-    ping_123456:
-      image: jonmorehouse/ping-pong
-      exposedPorts: [8089/tcp]
-</property>
-
-
-
-

Since now the ports are unique and names are unique, you can run tests using same orchestration in parallel against same docker host.

-
-
- - - - - -
- - -You can use the same approach for docker-compose files not only with cube format. But then your docker-compose will be tight to Arquillian Cube. The best approach if you want to use docker-compose format is using extends. -
-
-
- - - - - -
- - -Star operator must also used on enrichers for example: -@HostPort(containerName = "tomcat*", value = 8080) or -@DockerUrl(containerName = "tomcat*", exposedPort = 8080) -
-
-
-
-
-
-

12. Container Object pattern

-
-
-

If you search for a description of what Page Object is, you’ll find that it describes a pattern that allows you to model content in a reusable and maintainable way. -The description also points that within your web app’s UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. -Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run.

-
-
-

As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the Container Object pattern. -You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. -For example:

-
-
-
    -
  • -

    the host IP where the container is running

    -
  • -
  • -

    the bounded port for a given exposed port

    -
  • -
  • -

    any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container.

    -
  • -
-
-
-

For example, if running a MySQL database in a container, it could be the user and password to access the database. -Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file.

-
-
-

As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects.

-
-
-

Before looking at how this pattern is implemented in Cube, let’s go through an example:

-
-
-

Suppose all your applications need to send a file to an FTP server. -To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. -One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container.

-
-
-

All these operations that involve the FTP server and container could be encapsulated inside a Container Object. -This container object might contain the following information:

-
-
-
    -
  • -

    which image is used

    -
  • -
  • -

    IP and port bound to the host where the FTP server is running

    -
  • -
  • -

    user and password required to access the FTP server

    -
  • -
  • -

    methods for asserting the existence of a file inside the container

    -
  • -
-
-
-

Test code will only interact with this object instead of directly hard coding all required information inside the test. -Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself.

-
-
-

Now let’s see how Arquillian Cube implements the Container Object pattern.

-
-
-

12.1. Arquillian Cube and Container Object

-
-

Let’s see a simple example of how you can implement a Container Object in Cube. -Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. -The Container Object will be a simple POJO with special annotations:

-
-
-
PingPongContainer.java
-
-
package org.superbiz.containerobject;
-
-@Cube(value = "pingpong", portBinding = "5000->8080/tcp") (1)
-@CubeDockerFile
-public class PingPongContainer {
-
-  @HostIp (2)
-  String dockerHost;
-
-  @HostPort(8080)
-  private int port;
-
-  public URL getConnectionUrl() { (3)
-    try {
-      return new URL("http://" + dockerHost + ":" + port);
-          } catch (MalformedURLException e) {
-                  throw new IllegalArgumentException(e);
-          }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1The @Cube annotation configures the Container Object
2A Container Object can be enriched with Arquillian enrichers
3The Container Object hides how to connect to the PingPong server
-
-
-

The @Cube annotation is used to configure a Container Object. -Its value property is used to specify how the started container will be named (in this example pingpong) while the portBinding property can be used to specify the port binding information for the container instance (in this case 5000→8080/tcp). -Notice that portBinding can also accept an array, in which case more than one port binding definitions can be specified.

-
-
-

The next annotation is @CubeDockerFile, which configures how the container is created. -This example will use a Dockerfile located at the default class path location. -As the default location is the package+classname, in our example the Dockerfile should be placed at org/superbiz/containerobject/PingPongContainer. -It is possible to set any other class path location by passing it as the value property of the @CubeDockerFile annotation.

-
-
- - - - - -
- - -The @CubeDockerFile annotation defines the location where the Dockerfile is found, not the file itself. -This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. -
-
-
-

Any Cube can be enriched with any client side enricher. In the previous example a @HostIp enricher is used, but it could be enriched similarly with @CubeIp (which works similar to @HostPort), or a DockerClient instance if the field is annotated with @ArquillianResource.

-
-
-

Finally the @HostPort is used to translate the exposed port to the bound port. -In this example the port value will be 5000. Later you will learn briefly why this annotation is important.

-
-
-

After creating the container object, you can start using it in your test:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @Cube
-    PingPongContainer pingPongContainer;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        String pong = ping();
-        assertThat(pong, containsString("OK"));
-        assertThat(pingPongContainer.getConnectionPort(), is(5000));
-    }
-}
-
-
-
-

The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with @Cube. -Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. -Notice that this @Cube annotation is exactly the same as the one used when you defined the Container Object. -Placing a @Cube annotation on the field will allow you to override any property of the Container Object from the test side. -Due to the override mechanism, it is important to use the @HostPort annotation when the bound port is needed, since it can be changed from the test definition.

-
-
- - - - - -
- - -The Container Object pattern only works in Client mode or Arquillian Standalone. -
-
-
-

12.1.1. ShrinkWrap Dockerfile Descriptor

-
-

If you want you can use ShrinkWrap Dockerfile descriptor to create the Dockerfile file. -First thing you need to do is adding shrinkwrap-descriptord-api-docker dependencies:

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-api-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-<dependency>
-  <groupId>org.jboss.shrinkwrap.descriptors</groupId>
-  <artifactId>shrinkwrap-descriptors-impl-docker</artifactId>
-  <scope>test</scope>
-</dependency>
-
-
-
-

And in similar way you use @Deployment in Arquillian test, you can use @CubeDockerFile annotation in a public static method to define Dockerfile file and elements required for creating the container programmatically:

-
-
-
PingPongContainer.java
-
-
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
-public class PingPongContainer {
-
-  @CubeDockerFile (1)
-  public static Archive<?> createContainer() { (2)
-    String dockerDescriptor =   Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString();
-      return ShrinkWrap.create(GenericArchive.class)
-                .add(new StringAsset(dockerDescriptor), "Dockerfile"); (3)
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1@CubeDockerFile annotation is used.
2Method must be public and static.
3Returns a GenericArchive with all elements required for building the Docker container instance.
-
-
-

As part of Arquillian Cube, we are providing a org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset asset. -This asset is similar to org.jboss.shrinkwrap.api.asset.UrlAsset, but the former caches to disk for an amount of time the content that has been downloaded from the URL. -By default this expiration time is 1 hour, and it is configurable by using the proper constructor.

-
-
-
- -
-

A Container Object can reference more Container Objects from inside of it. -Effectively, a Container Object can be an aggregation of other Container Objects:

-
-
-
FirstContainerObject.java
-
-
@Cube
-public class FirstContainerObject {
-
-  @Cube("inner") (1)
-  LinkContainerObject linkContainerObject;
-
-}
-
-
-
- - - - - -
1Cube definition inside another Cube.
-
-
-

In this case Arquillian Cube will create a link from FirstContainerObject to LinkContainerObject with link value inner:inner.

-
-
-

Of course you can override the link value using @Link annotation.

-
-
-
-
@Cube("inner")
-@Link("db:db")
-TestLinkContainerObject linkContainerObject;
-
-
-
-
-

12.1.3. Image

-
-

So far, you’ve seen that a Container Object creates an instance from a Dockerfile using @CubeDockerFile annotation. You can also create a Container Object from an image by using @Image annotation:

-
-
-
ImageContainerObject.java
-
-
@Cube("tomme")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public static class ImageContainerObject {
-}
-
-
-
-

In this case Arquillian Cube will create containers based on the image defined in the annotation.

-
-
-
-

12.1.4. Environment

-
-

You can set environment variables using @Environment annotation at field or object level:

-
-
-
-
@Environment(key = "C", value = "D")
-@Environment(key = "A", value = "B")
-@Image("tomee:8-jre-1.7.2-webprofile")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.5. Volume

-
-

You can set environment variables using @Volume annotation at field or object level:

-
-
-
-
@Image("tomee:8-jre-1.7.2-webprofile")
-@Volume(hostPath = "/mypath", containerPath = "/containerPath")
-@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
-public class ImageContainer {
-}
-
-
-
-
-

12.1.6. Creating container objects dynamically

-
-

Up to this point you have seen how to automatically inject Container Objects in tests. -Arquillian Cube also allows creating container objects from code. -In the next example, the original PingPongTest has been rewritten to create the Container Object inside the test method.

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    ContainerObjectFactory factory; (1)
-
-    @ArquillianResource
-    CubeController cubeController;
-
-    @Test
-    public void shouldReturnOkAsPong() throws IOException {
-        PingPongContainer pingPongContainer =
-            factory.createContainer(PingPongContainer.class); (2)
-        try {
-            String pong = ping();
-            assertThat(pong, containsString("OK"));
-            assertThat(pingPongContainer.getConnectionPort(), is(5000));
-        } finally {
-            cubeController.stop("pingpong"); (3)
-            cubeController.destroy("pingpong");
-        }
-    }
-}
-
-
-
- - - - - - - - - - - - - -
1A ContainerObjectFactory instance is injected into the test.
2The injectect factory instance is used to instantiate a container object.
3A CubeController could be used to stop the associated docker container.
-
-
-

Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created.

-
-
-
-
-

12.2. Arquillian Cube and Container Object DSL

-
-

Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. -Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects.

-
-
-

With DSL you can generate container objects or network objects as well.

-
-
-

12.2.1. Container Objects DSL

-
-

To create a generic container object you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Container and annotate it with @DockerContainer. -Now you need to provide the full definition of the container using the DSL. -Let’s see how to use it following the previous Ping Pong example.

-
-
-
PingPongTest.java
-
-
@DockerContainer (1)
-Container pingpong = Container.withContainerName("pingpong") (2)
-                        .fromImage("jonmorehouse/ping-pong")
-                        .withPortBinding(8080)
-                        .build();
-
-@Test
-public void should_return_ok_as_pong() throws IOException {
-   String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); (3)
-    assertThat(response).containsSequence("OK");
-}
-
-
-
- - - - - - - - - - - - - -
1Annotate field with DockerContainer
2Start the DSL using withContainerName method
3You can get Docker Host Ip address and binding port for given exposed port
-
-
- - - - - -
- - -In case you define more than one generic container and want to start them in a specfic order, there is order attribute in annotation to specify it. The higher the number, the sooner the container is started. -
-
-
- - - - - -
- - -Container objects by default are started and stopped in class scope. To change it to method scope you can use .withConnectionMode call. -
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Container DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the container object using an special JUnit Rule.

-
-
-
RedisTest.java
-
-
@ClassRule
-public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
-                                               .withPortBinding(6379);
-
-Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379));
-jedis.set("foo", "bar");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-

In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported.

-
-
-
-
@ClassRule
-public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*")
-                                                .withPortBinding(6379);
-
-
-
-
-
-

12.2.2. Network Objects DSL

-
-

To create a network using DSL approach you only need to create a field of type org.arquillian.cube.docker.impl.client.containerobject.dsl.Network and annotate it with org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork. -Let’s see how to create a new network with default driver.

-
-
-
NetworkTest.java
-
-
@DockerNetwork (1)
-Network network = Network.withDefaultDriver("mynetwork").build(); (2)
-
-
-
- - - - - - - - - -
1Annotate field with DockerNetwork
2Start the DSL using withDefaultDriver method
-
-
-
JUnit Rule
-
-

If you are using JUnit and want to use Network DSL builder approach, you can use JUnit Rule instead of Arquillian Runner.

-
-
-

To do use it you need to define the network object using an special JUnit Rule.

-
-
-
NetworkTest.java
-
-
@ClassRule
-public static final NetworkDslRule network = new NetworkDslRule("mynetwork");
-
-
-
- - - - - -
- - -You need to add org.arquillian.cube:rquillian-cube-docker-junit-rule dependency. -
-
-
-
-
-
-
-
-

13. Arquillian Standalone and Cube

-
-
-

You can use Arquillian Standalone with Arquillian Cube too. -Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. -Basically it means no @Deployment static method, and tests runs as client implicitly.

-
-
-

Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. -Internally Arquillian Cube implicitly defines the autostartContainers property (unless you define it), with regexp:.* expression, which means all containers will be created/started. -If you want to avoid this behavior, you can set this property to [none]. -As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own.

-
-
-

Dependencies you need to set for Standalone mode are:

-
-
-
pom.xml
-
-
<dependencies>
-    <dependency>
-        <groupId>org.jboss.arquillian.junit</groupId>
-        <artifactId>arquillian-junit-standalone</artifactId> (1)
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-         <groupId>org.arquillian.cube</groupId>
-         <artifactId>arquillian-cube-docker</artifactId>
-         <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
-
- - - - - -
1You need to change arquillian-junit-container to standalone.
-
-
-
-
-

14. Requirements Module

-
-
-

Arquillian Cube tries to adapt to docker installation you have. -For example if you are running Docker in linux machine, Docker Cube is going to try to connect there. -If it is in MacOS or Windows will try to use docker-machine, and if there is only one machine defined, it will use that one.

-
-
-

But sometimes this automatic resolutions are not possible, for example you have more than one docker machine installed/started, or you don’t know if the user is going to have one docker machine installed/started or more than one. -In these cases you need to set using machineName property which docker machine you want to use.

-
-
-

The problem is that you don’t know if the environment were test is running will have this machine or not. -If it doesn’t have then an exception will be thrown regarding cannot connect to given docker machine and test will fail.

-
-
-

Obviously this is not a failure, but test should be ignored. -For this use case Arquillian Cube and other ones, Cube implements the requirements module. -This module makes skip tests instead of failing in case of not meeting some environment expectations.

-
-
-

14.1. Example of environment requirements

-
-

With Requirements you can set if you want to skip tests if some environment or property variables are not set. -This is useful for example if you require that DOCKER_HOST, DOCKER_TLS_VERIFY or DOCKER_CERT_PATH system or environment variable must be set.

-
-
-

Notice that Cube takes precedence these variables over configured in arquillian.xml.

-
-
-

To use it you need to add requirements dependency.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-requirement</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Then you can use an special runner or a JUnit rule. -If you use Rule, the scope of annotations are only a test class, if you use the runner then annotations can be used in a suite level.

-
-
-

Let’s see how to use with Rule.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.RequirementRule;
-
-@RunWith(Arquillian.class)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (1)
-public class HelloWorldServletTest {
-
-    @Rule (2)
-    public RequirementRule requirementRule = new RequirementRule();
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Checks if it is set a system property and if not environment variable with name DOCKER_HOST
2Rule definition
-
-
-

But you can use the runner approach to use it suite level.

-
-
-
-
import org.arquillian.cube.requirement.RequiresSystemPropertyOrEnvironmentVariable;
-import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-
-@RunWith(ArquillianConditionalRunner.class) (1)
-@RequiresSystemPropertyOrEnvironmentVariable(value = {"DOCKER_HOST"}) (2)
-public class HelloWorldServletTest {
-
-    //....
-
-}
-
-
-
- - - - - - - - - -
1Runner for requirements check
2Checks if it is set a system property and if not environment variable with name DOCKER_HOST
-
-
-

Other annotations you can use are: RequiresEnvironmentVariable and RequiresSystemProperty which basically instead of checking as system property or environment variable, they only checks ones.

-
-
-
-

14.2. Example with Docker

-
-

But also there is an annotation in docker module for checking the environment against docker machine.

-
-
-
-
import org.arquillian.cube.requirement.ArquillianConditionalRunner;
-import org.arquillian.cube.docker.impl.requirement.RequiresDockerMachine;
-
-@RunWith(ArquillianConditionalRunner.class)
-@RequiresDockerMachine(name = "dev") (1)
-public class HelloWorldServletTest {
-
-    //....
-}
-
-
-
- - - - - -
1Docker machine requirement
-
-
-

Previous test will only be executed if in the environment where test is running has docker machine installed with a machine named dev.

-
-
-
-

14.3. Customizing Requirements

-
-

You can also implement your own requirement annotations. -To do it you only need to do two things.

-
-
-
    -
  • -

    An annotation annotated with org.arquillian.cube.spi.requirement.Requires pointing to a class which implements org.arquillian.cube.spi.requirement.Requirement.

    -
  • -
  • -

    An implementation of org.arquillian.cube.spi.requirement.Requirement interface.

    -
  • -
-
-
-

Let’s see an example of how to implement a requirement against docker version.

-
-
-
RequiresDockerVersion.java
-
-
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-@Requires(DockerRequirement.class) (1)
-public @interface RequiresDockerVersion {
-    String name() default ""; (2)
-}
-
-
-
- - - - - - - - - -
1Sets requirement interface
2Attribute to set the required version
-
-
-

And the implementation logic:

-
-
-
DockerRequirement.java
-
-
public class DockerRequirement implements Requirement<RequiresDockerVersion> {
-
-    @Override
-    public void check(RequiresDockerMachine context) throws UnsatisfiedRequirementException {
-        String name = context.name();
-        if (name != null && !name.isEmpty()) {
-            String installedVersion = getDockerVersion();
-
-            if (!name.equals(installedVersion)) {
-                throw new UnsatisfiedRequirementException("Docker version not specified."); (1)
-            }
-        }
-    }
-}
-
-
-
- - - - - -
1In case of not meeting an expectation, org.arquillian.cube.spi.requirement.UnsatisfiedRequirementException should be thrown with a message.
-
-
-

After that you can use this annotation as any other requirements provided by Cube.

-
-
-
-
-
-

15. Containerless Server and Docker

-
-
-

In all previous sections we have seen that the application is deployed inside a container. -For example in case of Tomcat application, resources are deployed inside a Servlet container or for example in case of Apache TomEE you can deploy EJBs inside an EJB container.

-
-
-

But nowadays there other kind of applications that contains the container (if they have one) embedded inside them. -Typically these applications uses an embedded server and they are run as CLI applications. -Some examples can be Spring Boot, Netty, SparkJava or Undertow.

-
-
-

If you are using some of these technologies with Docker, you can still use Arquillian Cube to write your tests.

-
-
-

15.1. Java Embedded Servers

-
-

Let’s suppose we are writing a service which should return as text the current day and time. -To serve this service to the world we decide to use undertow embedded server.

-
-
-

The code looks like:

-
-
-
DaytimeServer.java
-
-
import io.undertow.Undertow;
-import io.undertow.server.HttpHandler;
-import io.undertow.server.HttpServerExchange;
-import io.undertow.util.Headers;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class DaytimeServer {
-
-  public static void main(String[] args) { (1)
-
-      Undertow server = Undertow.builder()
-              .addHttpListener(8080, "0.0.0.0")
-              .setHandler(new HttpHandler() {
-                @Override
-                public void handleRequest(final HttpServerExchange exchange) throws Exception {
-                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
-                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
-                    exchange.getResponseSender().send(simpleDateFormat.format(new Date()) + System.lineSeparator()); (2)
-                }
-            }).build();
-       server.start();
-  }
-}
-
-
-
- - - - - - - - - -
1This class is a CLI application.
2Returns a text with the day and time formatted with SimpleDateFormat.
-
-
-

See that this application is a CLI application which is pretty different from previous examples. -Previously the packaged application was deployed inside an application server, which in fact means that Arquillian connects to the server and tells it to deploy that file.

-
-
-

In this example there is no application server nor servlet server waiting for Arquillian to deploy an archive but the application is self-contained, it contains everything. -So in fact if you want to run the application probably you will end up by doing something like java -jar daytime.jar.

-
-
-

So how to write a test for these classes if we are using Docker as runtime container?

-
-
-

The first thing to do is add arquillian-cube-containerless dependency.

-
-
-
pom.xml
-
-
<dependency>
-  <groupId>org.arquillian.cube</groupId>
-  <artifactId>arquillian-cube-containerless</artifactId>
-  <version>${arquillian.cube.version}</version>
-</dependency>
-
-
-
-

Next step is creating a Dockerfile. -This is required because we need to set not only the container image to be used but how to run the application. -But see that there is a problem on creating a Dockerfile in this case. -The jar name is not static because it will depend on the name you give during the creation of the archive (using Shrinkwrap). -So in fact Dockerfile should be templaterized. -And this is something that Arquillian Cube can do for you. -The idea is creating a file called DockerfileTemplate.

-
-
-
src/test/resources/daytime/DockerfileTemplate
-
-
FROM java:7
-
-WORKDIR /usr/src/server
-COPY ${deployableFilename} /usr/src/server/${deployableFilename} (1)
-EXPOSE 8080
-CMD ["java", "-jar", "${deployableFilename}"]
-
-
-
- - - - - -
1${deployableFilname} will be replaced at runtime by the name of the jar file created by Shrinkwrap.
-
-
-

Then we need to touch arquillian.xml file by setting an special container definition so Arquillian doesn’t crash because of trying to deploy the archive into a none defined container.

-
-
-
src/test/resources/arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers"> (1)
-      daytime:
-        buildImage: (2)
-          dockerfileLocation: src/test/resources/undertow (3)
-          noCache: true
-          remove: true
-        await:
-          strategy: polling
-        portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true"> (4)
-    <configuration>
-        <property name="containerlessDocker">daytime</property> (5)
-        <property name="embeddedPort">8080</property> (6)
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1The Docker container is defined as per usual.
2buildImage attribute is used to define the dockerfile location.
3This attribute sets the directory where the Dockerfile is stored. In fact in this case it is the directory where DockerfileTemplate file is stored.
4A container provided by Arquillian Cube must be defined.
5This property is used to set which container must be started.
6This property sets the exposed port by the embedded server.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the test:

-
-
-
DaytimeTest.java
-
-
@RunWith(Arquillian.class)
-public class DaytimeTest {
-
-  private static final String LINE_SEPARATOR = System
-          .getProperty("line.separator");
-
-  @Deployment(testable = false) (1)
-  public static JavaArchive createDeployment() {
-      JavaArchive[] undertow = Maven.resolver().resolve("io.undertow:undertow-core:1.1.1.Final").withTransitivity().as(JavaArchive.class); (2)
-
-      JavaArchive jar = ShrinkWrap
-              .create(JavaArchive.class, "daytime.jar")
-              .addClass(DaytimeServer.class); (3)
-
-      for (JavaArchive javaArchive : undertow) { (4)
-          jar.merge(javaArchive);
-      }
-
-      jar.addAsManifestResource(
-              new StringAsset(
-                      "Main-Class: org.arquillian.cube.impl.containerless.DaytimeServer"
-                              + LINE_SEPARATOR), "MANIFEST.MF"); (5)
-      return jar;
-  }
-
-  @Test
-  public void shouldReturnDateFromDaytimeServer(@ArquillianResource URL base) { (6)
-      try (
-              BufferedReader in = new BufferedReader(new InputStreamReader(
-                    base.openStream()));) {
-          String userInput = in.readLine();
-          assertThat(userInput, notNullValue());
-      } catch (UnknownHostException e) {
-          fail("Don't know about host ");
-      } catch (IOException e) {
-          fail("Couldn't get I/O for the connection to ");
-      }
-  }
-}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
1Tests should be run as-client.
2ShrinkWrap Maven resolver gets all dependencies for Undertow.
3Create a jar file called daytime.jar with DaytimeServer class.
4Undertow dependencies are merged inside jar.
5Because it is a runnable jar, MANIFEST is created accordantly.
6Simple test.
-
-
-
-
-
-

16. Cube Docker Drone/Graphene Integration

-
-
-

16.1. Drone

-
-

The Arquillian Drone extension for Arquillian provides a simple way how to include functional tests for your application with a web-based user interface. -Arquillian Drone brings the power of WebDriver into the Arquillian framework.

-
-
-

Arquillian Drone provides some features that make it better to use instead of plain WebDriver:

-
-
-
    -
  • -

    Life cycle management of the browser

    -
  • -
  • -

    Interaction with deployments and containers provided by Arquillian

    -
  • -
  • -

    Simple usage of multiple browsers in a single test

    -
  • -
  • -

    Configuration kept on a single place, outside of the Java code

    -
  • -
  • -

    Fully compatible with the IDE

    -
  • -
  • -

    Support for injection of Pages, PagesFragments, AJAX request guards and more via Arquillian Graphene 2

    -
  • -
  • -

    Integration with mobile based browsers testing (Arquillian Droidium)

    -
  • -
  • -

    Integration of JavaScript test suite execution (QUnit)

    -
  • -
  • -

    Compatible with WebDriver (Selenium 2) and Selenium Grids

    -
  • -
-
-
-

You can read more about Drone at https://docs.jboss.org/author/display/ARQ/Drone

-
-
-

16.1.1. Integration with Cube Docker

-
-

Arquillian Cube Docker is the Arquillian extension for managing Docker containers from your test.

-
-
-

Selenium offers Docker images for Selenium Standalone Server with Chrome or Firefox installed. -You can check available the images at https://github.com/SeleniumHQ/docker-selenium.

-
-
-

With these images and using Selenium WebDriver you can run your functional tests for web UI applications without having to install any browser.

-
-
-

It seems logic an integration of Arquillian Drone with Cube, so you can use all powerful features of Drone, and let Cube configure Docker things for you.

-
-
-

This integration does next things for you:

-
-
-
    -
  1. -

    Inspect classpath to get selenium version used

    -
  2. -
  3. -

    Starts a docker container with configured browser in Drone and same selenium version as your JARs. If not Firefox is used

    -
  4. -
  5. -

    Provides a WebDriver that is able to connect to this container

    -
  6. -
  7. -

    If configured (by default it is) a VNC recorder container is started so each test is recorded in a mp4 file.

    -
  8. -
-
-
-
-

16.1.2. Configuration

-
-

Apart from using Drone configuration properties for configuring browser, Cube Docker can be customized with some specific attributes:

-
-
-
-
recordingMode
-
-

Configures mode for recording. The valid values are: ALL, ONLY_FAILING, NONE. Default value is `ALL.

-
-
videoOutput
-
-

Directory where videos are stored. By default is target/reports/videos or if target does not exists build/reports/videos and if not creates a target/reports/videos by default.

-
-
browserImage
-
-

Docker image to be used as custom browser image instead of the official one (https://github.com/SeleniumHQ/docker-selenium). Notice that browser property will be used for setting WebDriver capabilities.

-
-
browserDockerfileLocation
-
-

Dockerfile location to be used to built custom docker image instead of the official one. This property has preference over browserImage.

-
-
containerNameStrategy
-
-

Sets the strategy for generating the container name. Valid values are STATIC, STATIC_PREFIX, RANDOM with the default being STATIC. STATIC will always use the same name, STATIC_PREFIX lets you define a prefix and enables running different tests in parallel and RANDOM generates a unique container name on every test run enabling running the same test in parallel.

-
-
containerNamePrefix
-
-

The prefix for the STATIC_PREFIX container name strategy.

-
-
-
-
- - - - - -
- - -Your custom images must expose the webdriver port 4444 and if you plan to use VNC, expose the default port 5900 as well -
-
-
-

An example of configuration might look like:

-
-
-
arquillian.xml
-
-
<arquillian>
-    <extension qualifier="cubedrone">
-        <property name="recordingMode">NONE</property>
-    </extension>
-</arquillian>
-
-
-
-
-

16.1.3. Example

-
-

For adding Drone Cube integration you only need a Cube project with a docker-compose or cube file only specifying business containers.

-
-
-
-
helloworld:
-  image: dockercloud/hello-world
-  ports:
-    - "80:80"
-
-
-
-

Configure the Cube extension:

-
-
-
arquillian.xml
-
-
<extension qualifier="docker">
-    <property name="machineName">dev</property> (1)
-    <property name="dockerContainersFile">docker-compose.yml</property>
-</extension>
-
-
-
- - - - - -
1In case of using more than one docker machine instance, you need to set it manually.
-
-
-

And finally among cube dependencies, add the drone, selenium and cube-drone dependencies.

-
-
-
pom.xml
-
-
<dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.jboss.arquillian.extension</groupId>
-            <artifactId>arquillian-drone-bom</artifactId>
-            <version>2.0.0.Final</versio>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.jboss.arquillian.selenium</groupId>
-            <artifactId>selenium-bom</artifactId>
-            <version>2.53.1</version>
-            <type>pom</type>
-            <scope>import</scope>
-        </dependency>
-    </dependencies>
-</dependencyManagement>
-
-<dependencies>
-    <dependency>
-        <groupId>org.arquillian.cube</groupId>
-        <artifactId>arquillian-cube-docker-drone</artifactId>
-        <scope>test</scope>
-    </dependency>
-    <dependency>
-        <groupId>org.jboss.arquillian.extension</groupId>
-        <artifactId>arquillian-drone-webdriver-depchain</artifactId>
-        <version>2.0.0.Final</version>
-        <type>pom</type>
-        <scope>test</scope>
-    </dependency>
-</dependencies>
-
-
- -
-

Full source code of usign custom image can be found at: https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-drone-custom

-
-
-
-
-

16.2. Graphene

-
-

Arquillian Graphene is a set of extensions for the WebDriver API, focused on rapid development and usability in a Java environment. -Its API encourages people to write tests for AJAX-based web applications in a concise and maintainable way. -Graphene strives for reusable tests by simplifying the use of web page abstractions (Page Objects and Page Fragments). -You will get a taste of the Graphene API in just a minute!

-
-
-

16.2.1. Integration with Docker Cube

-
-

Arquillian Graphene depends on Drone to provide an instance of WebDriver, so everything that is valid in Integration with Cube Docker is also valid for Cube Graphene.

-
-
-

So what can offer Docker Cube integration to you?

-
-
-

Arquillian has in summary two operating modes:

-
-
-
-
Standalone
-
-

runs tests without container integration, only lifecycle of extensions is managed allows to use Graphene independently of Arquillian containers and deployment management. In terms of implementation means a test without @Deployment method, so it means that the artifact is already created and running. In case of Docker Cube means that docker image has been already created with the artifact inside it. You are in standalone mode if you add the arquillian-junit-standalone artifact.

-
-
Container
-
-

runs tests with container, managed lifecycle of container including deployment. In terms of Docekr Cube means that the deployment file is going to be deployed into a running Docker image. You are in standalone mode if you add the arquillian-junit-container artifact.

-
-
-
-
-

One of the things that Graphene offers to developers is to not have to worry about where the application is deployed by resolving automatically the host and the context of the application. -In summary developer does not need to worry about calling webdriver.get(…​) method since it is automatically called by Graphene. -Notice that this is a big difference with Drone where you need to call the get method Example.

-
-
-

This autoresolution only works in case of running with container mode since it knows everything from the point of view deployment. -But in case of using Standalone mode, since it doesn’t know anything from deployment, you need to use url configuration property to set the url to use in webdriver.get(..) method.

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://localhost:8080/myapp</property> (1)
-</extension>
-
-
-
- - - - - -
1Base URL of WebDriver
-
-
-

The problem is that in case of using Docker Cube (and more specifically docker-machine/boot2docker) is that probably you don’t know the docker host at configuration time but in runtime. -And this is where Docker Cube can help you when using Standalone mode.

-
-
-
-

16.2.2. URL configuration in Standalone mode

-
-

As noted in Graphene Configuration you need to configure the url parameter in case of using Graphene in Standalone mode. -This is quite difficult to do it with Docker Cube because you need to set the docker host address and you might not know at configuration time. -For this reason Docker Cube Graphene integration helps you on this following next rules:

-
-
-

url can use the dockerHost special word which will be replaced at runtime by docker host ip.

-
-
-

If url property starts with dockerHost resolution will be appended automatically at the start of the url.

-
-
-

Some examples (for now don’t think about ports since it is going to touch later):

-
-
- -
-
-

Previous examples has not take into consideration port thing. -The next thing to resolve is the port of the URL which in this case and since browser runs inside docker host means resolve exposed ports..

-
-
-

Port resolution follows next rules:

-
-
-
    -
  • -

    If url contains a port, that port is used. Notice that this port should be an exposed port.

    -
  • -
  • -

    If url has no port then 80 is used.

    -
  • -
-
-
-

In most cases you are going to use:

-
-
-
arquillian.xml
-
-
<extension qualifier="graphene">
-  <property name="url">http://helloworld:8080/myapp</property>
-</extension>
-
-
-
-

That configuration would be translated to http://<internalIpOfhelloworldContainer:8080/myapp and Graphene will use it as base for all WebDriver calls.

-
-
-
-

16.2.3. Example

-
-

Apart from adding arquillian, arquillian-drone, selenium-bom and arquillian-cube-docker-drone, obviously you also need to add the dependencies of Graphene.

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver</artifactId>
-    <version>2.1.0.Final</version>
-    <type>pom</type>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.jboss.arquillian.graphene</groupId>
-    <artifactId>graphene-webdriver-impl</artifactId>
-    <version>2.1.0.Final</version>
-    <scope>test</scope>
-</dependency>
-
-
-
-

You can see the same example we used in Drone but using Graphene at https://github.com/arquillian/arquillian-cube/tree/master/docker/ftest-graphene

-
- -
-
-
-
-
-

17. Rest-Assured integration

-
-
-

Rest-Assured is a Java DSL for easy testing of REST services. -An example of how to make a GET request and validate the JSON or XML response might be like:

-
-
-

get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

The problem with Rest Assured is that by default it assumes that host is localhost and port 8080. -This might be perfect when not using Docker but when using docker this assumptions might not be the most typical.

-
-
-

For example you may use another bind port rather than 8080, you can expose 8080 port but bind to another port. -Also you might run docker host in different ip rather than localhost, maybe because you are using docker machine or maybe because you are using an external docker host.

-
-
-

So in these cases you need to set in every request:

-
-
-

get("http://myhost.org:5000/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));

-
-
-

or you can also configure Rest-Assured:

-
-
-
-
RestAssured.baseURI = "http://myhost.org";
-RestAssured.port = 5000;
-
-
-
-

or using RequestSpecBuilder

-
-
-
-
spec = new RequestSpecBuilder()
-            .setContentType(ContentType.JSON)
-            .setBaseUri("http://localhost:8080/")
-            .build();
-
-
-
-

17.1. Why integration with Cube?

-
-

Previous approach works but it has some problems:

-
-
-
    -
  • -

    You need to repeat the same configuration properties in all tests you want to use Rest-Assured with Docker.

    -
  • -
  • -

    Requires some development interference of the developer, if it is running in docker machine needs to set one ip which might change in the future, or if running on native linux must be changed to localhost.

    -
  • -
  • -

    Any change on Rest-Assured configuration properties would make all tests fails.

    -
  • -
-
-
-

To fix these problems, you can use Arquillian Cube Docker RestAssured integration which creates a RequestSpecBuilder with correct values set.

-
-
-
-

17.2. Configuration

-
-

17.2.1. Dependencies

-
-

To use Arquillian Cube RestAssured integration you only need to add as dependency.

-
-
-
pom.xml
-
-
<dependency>
-     <groupId>org.arquillian.cube</groupId>
-     <artifactId>arquillian-cube-docker-restassured</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-
-

17.2.2. Configuration Parameters

-
-

By default, if your scenario is not complex you don’t need to configure anything else, but this extension also contains some configuration parameters that can be used.

-
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AttributeDefault ValueDescription

baseUri

<schema>://<dockerhost>

-

It is the base uri used in RestAssured. You can set an specific value or not set and let extension to configure it by default using auto-resolution system.

-

schema

http

-

Schema used in case of auto-resolution of baseUri

-

port

If from all running containers there is only one binding port (notice that exposed ports are not bound if not specified), then this is the value used. If there are more than one binding port then an exception is thrown.

-

Port to be used for communicating with docker host. By default this port must be the exposed port used in port binding. The extension will resolve for given exposed port which is the binding port. If it is not found then exposed port will be assumed as binding port too. For example using -p 8080:80 you need to set this property to 80 and extension will resolve to 8080.

-

exclusionContainers

-

If you want to use auto-resolution of the port attribute you might want to exclude that extension searches for binding ports in some containers (for example monitoring containers). This is a CSV property to set container names of al of them.

-

basePath

-

Base path (context) of the application

-

useRelaxedHttpsValidation

-

Configures RestAssured to use relaxed https validation. If attribute is present but with no value then it is applied to all protocols. If you put an string, only this protocol will be applied the relaxed rules.

-

authenticationScheme

-

Sets the authentication scheme. Possible values are: -* basic:username:password for basic auth. -* form:username:password for from auth. -* preemptive:username:password. -* certificate:url:password for cert auth. -* digest:username:password for digest auth. -* oauth:consumerKey:consumerSecret:accessToken:secretToken for oauth 1. -* oauth2:accessToken for oauth 2.

-
-
-

For example:

-
-
-
arquillian.xml
-
-
<extension qualifier="restassured">
-    <property name="port">80</property>
-</extension>
-
-
-
- - - - - -
- - -You can set any extension property using system properties or environment variables, this can be really helpful in case of setting authentication scheme and sensitive data. For example arq.extension.restassured.basePath=helloworld for setting basePath property. -
-
-
-

You can also use @DockerUrl enrichment for configuring the RequestSpecBuilder instead of relaying to arquillian.xml.

-
-
-
-
@DockerUrl(containerName = "pingpong", exposedPort = 8080)
-@ArquillianResource
-RequestSpecBuilder requestSpecBuilder;
-
-
-
-
-

17.2.3. Example

-
-

After setting the dependency and configuring the extension if required you can write your Arquillian Cube test as usually and use RestAssured without configuring it:

-
-
-

With next docker compose file which starts a ping pong server listening at root context:

-
-
-
docker-compose.yml
-
-
helloworld:
-  image: jonmorehouse/ping-pong
-  ports:
-    - "5000:8080"
-
-
-
-

You only need to do:

-
-
-
PingPongTest.java
-
-
@RunWith(Arquillian.class)
-public class PingPongTest {
-
-    @ArquillianResource
-    @DockerUrl(containerName = "helloworld", exposedPort = 8080)
-    RequestSpecBuilder requestSpecBuilder;
-
-    @Test
-    public void should_receive_ok_message() throws MalformedURLException, InterruptedException {
-        RestAssured
-            .given()
-            .spec(requestSpecBuilder.build())
-            .when()
-            .get()
-            .then()
-            .assertThat().body("status", equalTo("OK"));
-    }
-
-}
-
-
-
-

Notice that no ip nor port configuration are required since everything is managed and configured by Cube.

-
- -
-
-
-
-
-

18. Arquillian Reporter integration

-
-
-

Arquillian Reporter (https://github.com/arquillian/arquillian-reporter) project brings neat reports of your Arquillian tests.

-
-
-

Check Arquillian Reporter website to see the kind of reports you can generate and how to configure it.

-
-
-

Arquillian Cube integrates with Arquillian Reporter to provide in these reports some information about Cube environment.

-
-
-

To integrate Cube with Reporter, you only need to add arquillian reporter’s depchain dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
-

After that all cubes information will be added in the report. -Cubes are elements that are deployed into a system, for example a Pod or a Docker container.

-
-
-

For example in case of a Docker Cube it will report start and stop duration time, if it has failed or not and some container properties like ports, links, image name, entrypoint, network …​.

-
-
-

18.1. Arquillian Cube Docker Reporter

-
-

In previous section you’ve read that by just adding reporter dependency, you get integration between cube and reporter and some information about cube (for example a docker container) is reported.

-
-
-

But sometimes you need more information about the system and not each cube individually. -For this reason there is a docker cube reporter integration that adds on the report information specific to docker environment like the composition used during deployment or docker host information.

-
-
-

For this reason if you add next dependency too:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-docker-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-
-
-
-

Information about docker host and an schema of docker compositions will be added in the report.

-
-
-
-

18.2. Arquillian Cube Docker Drone Integration

-
-

In Cube Docker Drone/Graphene Integration you’ve read that you can execute web UI tests inside a docker container which contains the browser. -Also an screencast is recorded so you can review lately what has happened inside the container.

-
-
-

If you add previous dependency arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker drone project, then the report will contain the screencasts as well in the report, so you can play from the report the recordings as well.

-
-
-
-

18.3. Arquillian Cube Docker RestAssured Integration

-
-

If you add arquillian-cube-docker-reporter and arquillian-reporter-depchain in a cube docker RestAssured project, then the report will contain the request and response logs for all test methods.

-
-
-
-

18.4. Arquillian Cube Kubernetes Reporter

-
-

There is a arquillian cube kubernetes reporter integration that give report information about resources configuration and kubernetes session i.e. Namespace, Master URL, Replication Controllers, Pods, Services etc.

-
-
-

For this reason you have to add following dependency:

-
-
-
pom.xml
-
-
<dependency>
-    <groupId>org.arquillian.cube</groupId>
-    <artifactId>arquillian-cube-kubernetes-reporter</artifactId>
-    <scope>test</scope>
-</dependency>
-<dependency>
-    <groupId>org.arquillian.reporter</groupId>
-    <artifactId>arquillian-reporter-depchain</artifactId>
-    <version>${version.arquillian.reporter}</version>
-    <type>pom</type>
-</dependency>
-
-
-
- -
-
-
-

19. Polyglot Applications

-
-
-

In previous section we have seen that we can test any java CLI application that offers a socket connection. -But if you think clearly there is nothing that avoid Arquillian Cube to deploy applications developed in other languages like Node.js, Play, Ruby on Rails, …​

-
-
-

Let’s see an example on how you can use Arquillian Cube to test a Node.js hello world application.

-
-
-

First thing to do is create the Node.js application.

-
-
-
src/main/js/package.json
-
-
{
-  "name": "helloworld-server",
-  "version": "0.0.1",
-  "description": "A NodeJS webserver to run inside a docker container",
-  "author": "asotobu@gmail.com",
-  "license": "APLv2",
-  "dependencies": {
-      "express": "*"
-  },
-  "scripts": {"start": "node index.js"}
-}
-
-
-
-
src/main/js/index.js
-
-
var express = require('express');
-
-var app = express();
-
-app.get('/', function(req, res){
-  res.send('Hello from inside a container!');
-});
-
-app.listen(8080);
-
-
-
-

Then we need to define a DockerfileTemplate as we did for Undertow.

-
-
-
src/test/resources/node/DockerfileTemplate
-
-
FROM node:0.11.14
-
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-
-ADD ${deployableFilename} /usr/src/app (1)
-RUN npm install
-EXPOSE 8080
-
-CMD [ "npm", "start" ]
-
-
-
- - - - - -
1We need to use ADD command adding the deployed file instead of COPY. We are going to see why below.
-
-
-

Finally the arquillian.xml configuration file.

-
-
-
arquillian.xml
-
-
<?xml version="1.0"?>
-<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns="http://jboss.org/schema/arquillian"
-  xsi:schemaLocation="http://jboss.org/schema/arquillian
-  http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
-
-  <extension qualifier="docker">
-    <property name="serverVersion">1.12</property>
-    <property name="serverUri">http://localhost:2375</property>
-    <property name="dockerContainers">
-        node:
-          buildImage:
-            dockerfileLocation: src/test/resources/node
-            noCache: true
-            remove: true
-          await:
-            strategy: polling
-          portBindings: [8080/tcp]
-    </property>
-  </extension>
-
-  <container qualifier="containerless" default="true">
-    <configuration>
-      <property name="containerlessDocker">node</property> (1)
-      <property name="embeddedPort">8080</property>
-    </configuration>
-  </container>
-
-</arquillian>
-
-
-
- - - - - -
1This property is used to set which container must be started. In this case node.
-
-
- - - - - -
- - -If containerless definition only contains only one image, it is not necessary to use containerlessDocker property. At the same time if the image only exposes one port, it is not necessary to use embeddedPort proeprty to set the port. So in previous example you could avoid using containerlessDocker and embeddedPort. -
-
-
-

And finally the Arquillian test.

-
-
-
NodeTest.java
-
-
@RunWith(Arquillian.class)
-public class NodeTest {
-
-  @Deployment(testable = false) (1)
-  public static GenericArchive createDeployment() {
-    return ShrinkWrap.create(GenericArchive.class, "app.tar") (2)
-            .add(new ClassLoaderAsset("index.js"), "index.js")
-            .add(new ClassLoaderAsset("package.json"), "package.json");
-  }
-
-  @Test
-  public void shouldReturnMessageFromNodeJs(@ArquillianResource URL base) { (3)
-    try (BufferedReader in = new BufferedReader(new InputStreamReader(
-            base.openStream()));) {
-        String userInput = in.readLine();
-        assertThat(userInput, is("Hello from inside a container!"));
-    } catch (UnknownHostException e) {
-        fail("Don't know about host ");
-    } catch (IOException e) {
-        fail("Couldn't get I/O for the connection to ");
-    }
-  }
-}
-
-
-
- - - - - - - - - - - - - -
1Tests should be run as-client.
2GenericArchive with tar extension must be created using Shrinkwrap.
3Simple test.
-
-
- - - - - -
- - -GenericArchive must end with tar extension because it is expected by Arquillian Cube. When you use ADD in Dockerfile, Docker will untar automatically the file to given location. -
-
-
-
-
-

20. Kubernetes

-
-
-

The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.

-
-
-

20.1. Overview

-
-

This extension, wll create and manage a temporary namespace for your tests, apply all Kubernetes resources required to create your environment and once everything is ready it will run your tests. The tests will be enriched with resources required to access services. Finally when testing is over it will cleanup everything.

-
-
-

This extension, will not mutate neither your containers (by deploying, reconfiguring etc) nor your Kubernetes resources, and takes a black box approach to testing.

-
-
-
-

20.2. Modules

-
-

The main modules of this extension are the following:

-
-
-
    -
  • -

    Kubernetes (default feature set)

    -
  • -
  • -

    Openshift (work in progress)

    -
  • -
  • -

    Fabric8 Microservices Platform (Fabric8 label and annotation support)

    -
  • -
-
-
-
-

20.3. Features

-
-
    -
  • -

    Hybrid (in or out of Kubernetes/Openshift)

    -
  • -
  • -

    Advanced namespace management

    -
  • -
  • -

    Dependency management (for maven based projects)

    -
  • -
  • -

    Auto align with Docker Registry

    -
  • -
  • -

    Enrichers for:

    -
  • -
  • -

    Kubernetes/Openshift client

    -
  • -
  • -

    Pods

    -
  • -
  • -

    Replication Controllers

    -
  • -
  • -

    Services

    -
  • -
  • -

    Integration with Fabric8 Modules:

    -
  • -
  • -

    Fabric8 Maven Plugin

    -
  • -
  • -

    Microservices Platform

    -
  • -
  • -

    "Bring your own client" support

    -
  • -
-
-
-
-

20.4. Pre-requisites

-
-
    -
  • -

    To use kubernetes extension, your host should have running kubernetes cluster.

    -
  • -
  • -

    To use openshift extension, your host should have running openshift cluster.

    -
  • -
-
-
-
-

20.5. Configuring the extension

-
-

The plugin can be configured using the traditional arquillian.xml, via system properties or environment variables (in that particular order). -Which means that for every supported configuration parameter, the arquillian.xml will be looked up first, if it doesn’t contain an entry, the system properties will be used. -If no result has been found so far the environment variables will be used.

-
-
-

Note: When checking for environment variables, property names will get capitalized, and symbols like "." will be converted to "_". -For example foo.bar.baz will be converted to FOO_BAR_BAZ.

-
-
-

20.5.1. Kubernetes Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

kubernetes.master

URL

Any

The URL to the Kubernetes master

kubernetes.domain

String

OSE

Domain to use for creating routes for services

docker.registry

String

Any

The docker registry

namespace.use.current

Boolean (false)

Any

Don’t generate a namespace use the current instead

namespace.use.existing

String

Any

Don’t generate a namespace use the specified one instead

namespace.prefix

String (itest)

Any

If you don’t specify a namespace, a random one will be created, with this prefix

namespace.lazy.enabled

Bool (true)

Any

Should the specified namespace be created if not exists, or throw exception?

namespace.destroy.enabled

Bool (true)

Any

Flag to destroy the namespace after the end of the test suite

namespace.destroy.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to delete the namespace

namespace.destroy.timeout

Long

Any

Time to wait before destroying the namespace

namespace.cleanup.enabled

Bool (true)

Any

Flag to clean (delete resources) the namespace after the end of the test suite

namespace.cleanup.confirm.enabled

Bool (false)

Any

Flag to ask for confirmation to clean the namespace

namespace.cleanup.timeout

Long

Any

Time to wait when cleaning up the namespace

env.init.enabled

Bool (true)

Any

Flag to initialize the environment (apply kubernetes resources)

env.config.url

URL

Any

URL to the Kubernetes JSON/YAML (defaults to classpath resource kubernetes.json)

env.config.resource.name

String

Any

Option to select a different classpath resource (other than kubernetes.json)

env.script.env

String

Any

Key/Value pairs to pass to setup scripts as environment variables

env.setup.script.url

URL

Any

Option to select a shell script that will setup the environment

env.teardown.script.url

URL

Any

Option to select a shell script to tear down / cleanup the environment

env.dependencies

List

Any

Whitespace separated list of URLs to more dependency kubernetes.json

wait.timeout

Long (5mins)

Any

The total amount of time to wait until the env is ready

wait.poll.interval

Long (5secs)

Any

The poll interval to use for checking if the environment is ready

wait.for.service.list

List

Any

Explicitly specify a list of services to wait upon

ansi.logger.enabled

Bool (true)

Any

Flag to enable colorful output

logs.copy

Bool (false)

Any

Whether to capture the pods logs and save them into the filesystem - as individual files, one for each pod. Filenames will be "ClassName-[MethodName-]-PodName[-ContainerName].log". If the pod has multiple containers, one log file for each container will be created. Kubernetes events (kubectl get events) will also be captured if this flag is enabled. Filenames will end with -KUBE_EVENTS.log

logs.path

String

Any

Directory where to save the pods logs. Defaults to "target/surefire-reports".

kubernetes.client.creator.class.name

Bool (true)

Any

Fully qualified class name of a kubernetes client creator class (byon)

-
-
-

20.5.2. Openshift Configuration Parameters

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Option

Type

Env

Description

autoStartContainers

List

Any

Comma Separated List of Pods which you want to auto start

definitionsFile

String

Any

Definitions file path

proxiedContainerPorts

List

Any

Comma Separated List following Pod:containerPort OR Pod:MappedPort:ContainerPort

routerHost

String

Any

Define OpenShift router address thats is used to resolve the pod’s address and to get its route

enableImageStreamDetection

Bool (false)

Any

Define if you want that Arquillian Cube apply Image Stream files (*-is.yml) before deploying

-
-
-

20.5.3. Openshift DNS Naming Service

-
-

The OpenShift module provides a easy way to run tests against your public application’s route. -The Arquillian Naming Service allows you to run tests annotated with @RunsAsClient without have to add the routes -manually to your /etc/hosts to make its name resolvable. The arquillian cube generates a custon namespaces prefix -that will be used to define the application’s route when running your tests against an OpenShift instance, even if you specify -a namepsace manually it will be transparent and the application’s endpoint will be resolvable within your java tests.

-
-
-

To use it, you need to setup your tests to use the ArquillianNameService, you can either configure it inside your test or -by setting a System properties.

-
-
-

Configuring inside a test class:

-
-
-
SomethingCoolTest.java
-
-
@Before
-public void prepareEnv(){
-    System.setProperty("sun.net.spi.nameservice.provider.1", "dns,ArquillianCubeNameService");
-    System.setProperty("sun.net.spi.nameservice.provider.2","default");
-}
-
-
-
-

Or just setting the following System Properties: --Dsun.net.spi.nameservice.provider.1=dns,ArquillianCubeNameService -Dsun.net.spi.nameservice.provider.2=default

-
-
-
-
-

20.6. Namespaces

-
-

The default behavior of the extension is to create a unique namespace per test suite. The namespace is created Before the suite is started and destroyed in the end. -For debugging purposes, you can set the namespace.cleanup.enabled and namespace.destroy.enabled to false and keep the namespace around.

-
-
-

In other cases you may find it useful to manually create and manage the environment rather than having arquillian do that for you. -In this case you can use the namespace.use.existing option to select an existing namespace. This option goes hand in hand with env.init.enabled which can be -used to prevent the extension from modifying the environment.

-
-
-

Last but not least, you can just tell arquillian, that you are going to use the current namespace. In this case, arquillian cube will delegate to the Kubernetes Client that will use:

-
-
-
    -
  • -

    ~/.kube/config

    -
  • -
  • -

    /var/run/secrets/kubernetes.io/serviceaccount/namespace

    -
  • -
  • -

    the KUBERNETES_NAMESPACE environmnet variable

    -
  • -
-
-
-

to determine the current namespace.

-
-
-
-

20.7. Creating the environment

-
-

After creating or selecting an existing namespace, the next step is the environment preparation. This is the stage where all the required Kubernetes configuration will be applied.

-
-
-

20.7.1. How to run kubernetes with multiple configuration files?

-
-
    -
  1. -

    Out of the box, the extension will use the classpath and try to find a resource named kubernetes.json or kubernetes.yaml*. The name of the resource can be changed using the env.config.resource.name. -Of course it is also possible to specify an external resource by URL using the env.config.url.

    -
  2. -
  3. -

    While finding resource in classpath with property env.config.resource.name, cube will look into classpath with given name, if not found, then cube will continue to look into classpath under META-INF/fabric8/ directory. -Using this you can put multiple resources(openshift.json, openshift.yml) inside META-INF/fabric8, and choose only required one by specifying env.config.resource.name property.

    -
  4. -
  5. -

    Either way, it is possible that the kubernetes configuration used, depends on other configurations. It is also possible that your environment configuration is split in multiple files. -To cover cases like this the env.dependencies is provided which accepts a space separated list of URLs.

    -
  6. -
  7. -

    There are cases, where instead of specifying the resources, you want to specify some shell scripts that will setup the environment. For those cases you can use the env.setup.script.url / env.teardown.script.url to pass the -scripts for setting up and tearing down the environment. Note that these scripts are going to be called right after the namespace is created and cleaned up respectively. -Both scripts will be executed using visible environment variables the following:

    -
    -
      -
    • -

      KUBERNETES_MASTER

      -
    • -
    • -

      KUBERNETES_NAMESPACE

      -
    • -
    • -

      KUBERNETES_DOMAIN

      -
    • -
    • -

      DOCKER_REGISTRY

      -
    • -
    • -

      all host environment variables

      -
    • -
    • -

      all environment variables in arquillian.xml via env.script.env (as properties).

      -
    • -
    -
    -
  8. -
-
-
-

(You can use any custom URL provided the appropriate URL stream handler.)

-
-
-

Note: Out of the box mvn urls are supported, so you can use values like: mvn:my.groupId/artifactId/1.0.0/json/kubernetes (work in progress)

-
-
-

Also: If your project is using maven and dependencies like the above are expressed in the pom, the will be used automatically. (work in progress)

-
-
- - - - - -
- - -
-

Arquillian Cube Kubernetes needs to authenticate into Kubernetes. -To do it, Cube reads from ~/.kube/config user information (token, password).

-
-
-

For example in case of OpenShift you can use oc login --username=admin --password=admin for creating a token for connecting as admin, or oc config set-credentials myself --username=admin --password=admin for statically add the username and password and will communicate with Kubernetes to update the ~/.kube/config file with the info provided.

-
-
-

You can read more about Kubernetes config file at http://kubernetes.io/docs/user-guide/kubectl/kubectl_config/

-
-
-
-
-
-
-

20.8. Readiness and waiting

-
-

Creating an environment does not guarantee its readiness. For example a Docker image may be required to get pulled by a remote repository and this make take even several minutes. -Running a test against a Pod which is not Running state is pretty much pointless, so we need to wait until everything is ready.

-
-
-

This extension will wait up to wait.timeout until everything is up and running. Everything? It will wait for all Pods and Service (that were created during the test suite initialization) to become ready. -It will poll them every wait.poll.interval milliseconds. For services there is also the option to perform a simple "connection test" by setting the flag wait.for.service.connection.enabled to true. -In this case it will not just wait for the service to ready, but also to be usable/connectable.

-
-
-
-

20.9. Immutable infrastructure and integration testing

-
-

As mentioned in the overview, this extension will not try to deploy your tests, inside an application container. -It doesn’t need nor want to know what runs inside your docker containers, nor will try to mess with it. -It doesn’t even need to run inside Kubernetes (it can just run in your laptop and talk to the kubernetes master).

-
-
-

So what exactly is your test case going to test?

-
-
-

The test cases are meant to consume and test the provided services and assert that the environment is in the expected state.

-
-
-

The test case may obtain everything it needs, by accessing the Kubernetes resources that are provided by the plugin as @ArquillianResources (see resource providers below).

-
-
-
-

20.10. Resource Providers

-
-

The resource providers available, can be used to inject to your test cases the following resources:

-
-
-
    -
  • -

    A kubernetes client as an instance of KubernetesClient

    -
  • -
  • -

    Session object that contains information (e.g. the namespace) or the uuid of the test session.

    -
  • -
  • -

    Deployments (by id or as a list of all deployments created during the session, optionally filtered by label)

    -
  • -
  • -

    Pods (by id or as a list of all pods created during the session, optionally filtered by label)

    -
  • -
  • -

    Replication Controllers (by id or as a list of all replication controllers created during the session, optionally filtered by label)

    -
  • -
  • -

    Replica Sets (by id or as a list of all replica sets created during the session, optionally filtered by label)

    -
  • -
  • -

    Services (by id or as a list of all services created during the session, optionally filtered by label)

    -
  • -
-
-
-

The Openshift extension also provides:

-
-
-
    -
  • -

    Deployment Configs (by id or as a list of all deployment configs created during the session)

    -
  • -
-
-
-

Here’s a small example:

-
-
-
ExampleTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ExampleTest {
-
-     @ArquillianResource
-     KubernetesClient client;
-
-     @ArquillianResource
-     Session session;
-
-      @Test
-      public void testAtLeastOnePod() throws Exception {
-       assertThat(client).pods().runningStatus().filterNamespace(session.getNamespace()).hasSize(1);
-      }
-    }
-
-
-
-

The test code above, demonstrates how you can inject an use inside your test the KubernetesClient and the Session object. It also demonstrates the use of kubernetes-assertions which is a nice little library based on [assert4j](http://assertj.org) for performing assertions on top of the Kubernetes model.

-
-
-

The next example is intended to how you can inject a resource by id.

-
-
-
ResourceByIdTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByIdTest {
-
-     @ArquillianResource
-     @Named("my-serivce")
-     Service service;
-
-     @ArquillianResource
-     @Named("my-pod")
-     Pod pod;
-
-     @ArquillianResource
-     @Named("my-contoller")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example shows how to inject a resource filtering by label.

-
-
-
ResourceByLabelTest.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceByLabelTest {
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Service service;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     Pod pod;
-
-     @ArquillianResource
-     @WithLabel(name="app", value="my-app")
-     ReplicationController controller;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

The next example is intended to how you can inject a resource list.

-
-
-
ResourceListExample.java
-
-
    @RunWith(Arquillian.class)
-    public class ResourceListExample {
-
-     @ArquillianResource
-     ServiceList services;
-
-     @ArquillianResource
-     PodList pods;
-
-     @ArquillianResource
-     ReplicationControllerList controllers;
-
-      @Test
-      public void testStuff() throws Exception {
-       //Do stuff...
-      }
-    }
-
-
-
-

Now let’s see how can you inject OpenShift Client Service.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    Service service;
-
-    @Named("hello-openshift-service")
-    @PortForward
-    @ArquillianResource
-    URL url;
-
-    @Test
-    public void service_instance_should_not_be_null() throws Exception {
-        assertThat(service).isNotNull();
-    }
-
-    @Test
-    public void testStuff() throws Exception {
-        //Do stuff...
-    }
-}
-
-
-
-

In case of OpenShift, test can be enriched with OpenShiftClient.

-
-
-
OpenshiftExample.java
-
-
public class HelloWorldTest {
-
-    @ArquillianResource
-    OpenShiftClient client;
-
-}
-
-
-
-
-

20.11. Dealing with version conflicts

-
-

Arquillian Cube Kubernetes and Openshift modules, heavily rely on the Fabric8 Kubernetes/Openshift client. -This client is also used in wide range of frameworks, so its not that long of a shot to encounter version conflicts.

-
-
-

To eliminate such issues, arquillian as of 1.1.0 is using a shaded uberjar of the client which contains versioned package (with major and minor version).

-
-
-

All enrichers provided by the arquillian modules, are configured to work both with the internal types, but also with whatever version of the client that is found in the classpath.

-
-
-

Note: If your existing tests don’t have a dependency to the kubernetes-client, you will either need to add kubernetes-client, to your classpath or use the internal classes. It is recommended to do the first.

-
-
-
-
-
-

21. Fabric8

-
-
-

The Fabric8 extension can be used along with the Kubernetes extension to provide a tighter integration with the Fabric8 Microservices Platform

-
-
-

21.1. Additional Features

-
- -
-
-
-
-
-

22. JBoss Forge Arquillian Addon

-
-
-

Forge Arquillian Addon offers an integration with Arquillian Cube.

-
-
-

22.1. How to use it?

-
-
    -
  • -

    Install JBoss-Forge from here.

    -
  • -
  • -

    Install arquillian add-on. To install arquillian-addon, in the Forge CLI, run command: -addon-install-from-git --url https://github.com/forge/arquillian-addon.git --coordinate org.arquillian.forge:arquillian-addon

    -
  • -
-
-
-

Create project using:

-
-
-

project-new --named compose

-
-
-

To add Arquillian & Framework dependencies run command:

-
-
-

arquillian-setup --standalone --test-framework junit

-
-
-

To add Arquillian Cube dependencies run command:

-
-
-

arquillian-cube-setup --type docker-compose --file-path docker-compose.yml

-
-
-

Before running above command make sure that file provided with file-path is exists.

-
-
-

Now create a test using command:

-
-
-

arquillian-create-test --named MyDockerComposeTest --target-package org.arquillian.cube

-
-
-

After that you can enrich a given Arquillian test with respective annotations depending on selected type:

-
-
-

arquillian-cube-create-test --test-class org.cube.docker.MyDockerComposeTest

-
-
-

Same way you can create a test for Kubernetes, Docker,Openshift.

-
-
-

See above example in next terminal cast:

-
-
-
-105913 -
-
-
-
-
-
-

23. Future work

-
-
-

Some configuration parameters will be modified to fix any possible requirements. -Although we are going to try to not break compatibility with previous versions, we cannot guarantee until beta stage.

-
-
-

Feel free to use it and any missing feature, bug or anything you see , feel free to add a new issue.

-
-
-
-
- - - \ No newline at end of file diff --git a/index.html b/index.html index cda86bfc6..a0db87e5a 100644 --- a/index.html +++ b/index.html @@ -2,29 +2,25 @@ - + - + Arquillian Cube - +