== 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: [source, java] .CubeIDResourceProvider.java ---- @ArquillianResource CubeID containerId; ---- [source, java] .CubeResourceProvider.java ---- @ArquillianResource DockerClient dockerClient; ---- [source, java] .CubeControllerProvider.java ---- @ArquillianResource CubeController cubeController; ---- [source, java] .DockerHostProvider.java ---- @HostIp String ip; ---- [source, java] .DockerHostPortProvider.java ---- @HostPort(containerName = "tomcat", value = 8080) int tomcatPort; // gets the binding port for exposed port 8080 of container tomcat. ---- [source, java] .CubeIpProvider.java ---- @CubeIp(containerName = "tomcat") String ip; ---- When running Arquillian Cube with Standalone you can enrich the test with URL. [source, java] .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: [source, java] .RouterURLEnricher.java ---- @RouteURL("app-name") private URL url; ---- This annotation also has a `path` parameter which can change the path part of the injected URL. Furthermore, the annotation also provides the `namespace` parameter which can be used when a test needs the URL of a Route that exists in a namespace other than the namespace the test uses (which is the default behavior when no value is provided to `namespace`). or [source, java] .RouterURLEnricher.java ---- include::../openshift/ftest-openshift-resources-standalone/src/test/java/org/arquillian/cube/openshift/standalone/HelloWorldOpenShiftResourcesIT.java[tag=enricher_expression_resolver_example] ---- You can also use an additional annotation `@AwaitRoute` to wait until the route becomes available. That is, it responds with a known good HTTP status code in given timeout. To resolve expression `"${route.name}"`, it looks in hierarchy of `system property` -> `environment variable` -> `properties defined in arquillian.xml`. 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: [source, xml] .arquillian.xml ---- .... 192.168.10.10 ... ---- 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. URL will be `http://:/` === Docker Inside Docker / Docker On Docker If you are running your tests on your continuous integration/delivery server (for example on Jenkins or GitLab runners) 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 - DockerHost is **not** the machine where your test is running. 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. ==== 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. ==== 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` === 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: [source, xml] .arquillian.xml ---- manual_database: image: zhilvis/h2-db portBindings: [1521/tcp, 8181->81/tcp] ---- 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 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. === 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: [source, xml] .arquillian.xml ---- 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] wildfly:8.1.0.Final:remote admin Admin#70365 ---- <1> We 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: [source, java] .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: [source, xml] .arquillian.xml ---- regexp:a(.*) ---- 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. [source, java] ---- public class ChangeNameAutoStartParser implements AutoStartParser { // <1> @Inject // <2> public Instance cubeDockerConfigurationInstance; @Override public Map parse() { // <3> final DockerCompositions dockerContainersContent = cubeDockerConfigurationInstance.get().getDockerContainersContent(); final Map nodes = new HashMap<>(); final Set containersNames = new TreeSet<>(dockerContainersContent.getContainers().keySet()); for (String name : containersNames) { nodes.put(new StringBuilder(name).reverse().toString(), Node.from(name)); } return nodes; } } ---- <1> Need to implement AutoStartParser interface <2> You can Inject any element produced by Arquillian such as CubeDockerConfiguration or ContainerRegistry <3> Returns 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. [source, xml] .arquillian.xml ---- custom:org.arquillian.cube.docker.impl.client.ChangeNameAutoStartParser ---- === 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: [source, xml] .arquillian.xml ---- 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> ---- <1> Note 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. [source, xml] .arquillian.xml ---- configuration> localhost admin mypass ---- 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. NOTE: Automapping only works in case you want to change the default server port to a _Docker_ port forwarded port. === 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: [source, xml] .arquillian.xml ---- http://192.168.0.2:2756 ... configuration> dockerServerIp admin mypass ---- <1> We set the +serverUri+ as usually. <2> +dockerServerIp+ is replaced at runtime. The +host+ property will be replaced automatically to +192.168.0.2+. NOTE: This also works in case you set +serverUri+ using +boot2docker+ special word or by using the defaults. Read more about it <> and <>. 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: [source.xml] .arquillian.xml ---- configuration> admin mypass ---- And in case you are running on _Windows_ or _MacOS_, `host`property will be automatically set to the +boot2docker +_ip_. === 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..ip` with container ip. * `arq.cube.docker..internal.ip` with container internal ip. * `arq.cube.docker..port.` with binding port of giving exposed port.