== Kubernetes
The kubernetes extension helps you write and run integration tests for your Kubernetes/Openshift application.
=== Overview
This extension will create and manage one 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.
In addition to the main testing namespace additional secondary namespaces could be used during testing. Cube
would not modify them, but tests could be enriched with resources from secondary namespaces to access services in them
in case you need to verify changes made by services you are testing.
This extension will neither mutate your containers *(by deploying, reconfiguring etc)* nor your Kubernetes resources
and takes a black box approach to testing.
=== Modules
The main modules of this extension are the following:
- Kubernetes
- Openshift
- Fabric8 Microservices Platform (Fabric8 label and annotation support)
=== 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:
** link:https://fabric8.io/guide/mavenPlugin.html[Fabric8 Maven Plugin]
** link:https://fabric8.io/guide/fabric8DevOps.html[Microservices Platform]
- "Bring your own client" support
=== Pre-requisites
- To use kubernetes extension, your host should have running kubernetes cluster.
- To use openshift extension, your host should have running openshift cluster.
=== Setup
To use Kubernetes extension you need to register next dependency in your build tool: `org.arquillian.cube:arquillian-cube-kubernetes:${version}`.
To use OpenShift extension you need to register next dependency in your build tool: `org.arquillian.cube:arquillian-cube-openshift:${version}`.
=== 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**.
==== Kubernetes Configuration Parameters
You can configure Kubernetes by using any of the next configuration properties in `arquillian.xml`.
[source, xml]
.src/test/resources/arquillian.xml
----
----
[cols="2,1,1,3", options="header"]
|===
| Options | Type | Env | Description
| kubernetes.master | URL | Any | The URL to the Kubernetes master
| cube.username | String | Any | Username to log in server
| cube.password | String | Any | Password to log in server
| cube.auth.token | String | Any | Bearer token for authentication to the API server
| cube.api.version | String(v1) | Any | Version for API server
| cube.trust.certs | Boolean(true) | Any | Boolean to trust Certificate
| 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 testing namespace use the current instead
| namespace.use.existing | String | Any | Don't generate a testing namespace use the specified one instead
| namespace.prefix | String (itest) | Any | If you don't specify a testing namespace, a random one will be
created, with this prefix
| namespace.lazy.enabled | Bool (true) | Any | Should the specified testing namespace be created if not exists,
or throw exception?
| namespace.destroy.enabled | Bool (true) | Any | Flag to destroy the testing namespace after the end of the test
suite
| namespace.destroy.confirm.enabled | Bool (false) | Any | Flag to ask for confirmation to delete the testing namespace
| namespace.destroy.timeout | Long | Any | Time to wait before destroying the testing namespace
| namespace.cleanup.enabled | Bool (true) | Any | Flag to clean (delete resources) the testing namespace after the
end of the test suite
| namespace.cleanup.confirm.enabled | Bool (false) | Any | Flag to ask for confirmation to clean the testing namespace
| namespace.cleanup.timeout | Long | Any | Time to wait when cleaning up the testing 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.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 | Comma-separated list of URLs to more environment dependencies
kubernetes.json
| wait.enabled | Bool (true) | Any | Whether to wait until the env is ready
| 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 | Comma-separated list of additional services to wait upon
| ansi.logger.enabled | Bool (true) | Any | Flag to enable colorful output
| kubernetes.client.creator.class.name| Bool (true) | Any | Fully qualified class name of a kubernetes client
creator class (byon)
| 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".
|===
==== Openshift Configuration Parameters
When using OpenShift you can use `arquillian.xml` to configure ANY of the configuration properties introduced at <> mixed with some specific configuration parameters related to OpenShift.
In cas of using OpenShift, then you need to use `openshift` qualifier instead of `kubernetes`, but as noticed in previous paragraph you can use it to set any Kubernetes configuration parameters as well.
[source, xml]
.src/test/resources/arquillian.xml
----
----
[cols="2,1,1,3", options="header"]
|===
| 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
| enableImageStreamDetection | Bool (true) | Any | Enable detecting ImageStream resources located at target/XXX-is.(json/yaml)
| portForwardBindAddress | String (127.0.0.1) | Any | If using port forwarding you can set the host
| templateUrl | URL | Any | URL where template is stored. This template is executed before any execution
| templateLabels | CSV | Any | Sets a comma separated value of template labels in form =
| templateParameters | CSV | Any | Sets a comma separated value of template parameters in form =
| templateProcess | Bool (true) | Any | Sets if templates must be processed or not
| awaitRouteRepetitions | Int (1) | Any | If `@AwaitRoute` is used, this option specifies how many times in a row
the route must respond successfully to be considered available; useful in environments where the route intermittently fails for a short while at the beginning
|===
==== 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 custom 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 namespace 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:
[source, java]
.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`
==== OpenShift Annotations
OpenShift extension comes with some annotations that let you define resources at test level instead of globally.
===== `@Template`
A template describes a set of objects that can be parameterized and processed to produce a list of objects for creation by OpenShift.
You can set template location as configuration parameter or using `@Template` annotation at class level.
Here's a small example:
[source, java]
----
include::../openshift/ftest-template-standalone/src/test/java/org/arquillian/cube/openshift/standalone/HelloWorldTemplateIT.java[tag=openshift_template_example]
----
However in `@Template`, url can be set using `url = https://git.io/vNRQm` or using
`url = "classpath:hello-openshift.yaml"` if template is on Test class path.
===== `@OpenShiftResource`
You can apply OpenShift resources files before test execution.
These resources creation are meant to be used for resources that aren't tied to a living thing.
Examples of these are service accounts, credentials, routes, ...
The value can either be:
* link (https://www.github.com/alesj/template-testing/some.json)
* test classpath resource (classpath:some.json)
* or plain content ({"kind" : "Secret", ...}
You can use `@OpenShiftResource` either at class level which implies that resource is created before test class execution and they are deleted after class execution or at method level which implies that resources are created and deleted after each test method execution.
You can see an example of OpenShift resources usage at https://github.com/arquillian/arquillian-cube/blob/master/openshift/ftest-openshift-resources-standalone/src/test/java/org/arquillian/cube/openshift/standalone/HelloWorldOpenShiftResourcesIT.java
===== `@OpenShiftDynamicImageStreamResource`
The `@OpenShiftResource` annotation makes it possible to add image stream definitions. To run some specific tests
we need to be able to override the image stream definition and point to the container image we want to test.
Very often these images are stored in insecure registries and the tags applied are different (in many cases newer)
than expected by the original image stream definition.
For this purpose the `@OpenShiftDynamicImageStreamResource` was created. We can create an image stream definition
by providing only required information, without the need to construct the JSON or YAML object expected by OpenShift.
Required resource will be created dynamically and deployed in OpenShift together with other resources.
[cols="2,1", options="header"]
|===
| Parameter | Description
| `name` | Image stream name
| `version` | Image stream version
| `image` | Image name with registry and tag
| `insecure` | If the registry is insecure, by default `true`
|===
[source, java]
.OpenShiftDynamicImageStreamResourceTest.java
----
@RunWith(Arquillian.class)
@Template(url = "https://raw.githubusercontent.com/${template.repository:jboss-openshift}/application-templates/${template.branch:master}/eap/eap71-sso-s2i.json")
@OpenShiftDynamicImageStreamResource(name = "${imageStream.eap64.name:jboss-eap64-openshift}", image = "${imageStream.eap64.image:registry.access.redhat.com/jboss-eap-6/eap64-openshift:1.8}", version = "${imageStream.eap64.version:1.8}")
public class OpenShiftDynamicImageStreamResourceTest {
@Test
public void testStuff() throws Exception {
//Do stuff...
}
}
----
=== Namespaces
The default behavior of the extension is to create a unique testing 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 testing 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 testing 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 as testing namespace.
In this case, arquillian cube will delegate to
https://github.com/fabric8io/kubernetes-client/[Kubernetes Client] that in turn will use:
- `~/.kube/config`
- `/var/run/secrets/kubernetes.io/serviceaccount/namespace`
- the `KUBERNETES_NAMESPACE` environmnet variable
to determine the current testing namespace.
In addition to the primary testing namespace a number of secondary namespaces could participate in tests, but only as
a possible location of resources needed to be accessed during tests, usually with verifying purpose. When the service
you are testing made some changes in state of services in another namespaces, you can specify the secondary
namespace in a field of `@Named` annotation to reach them.
[NOTE]
====
Necessary configuration to avoid permission conflicts for creating namespaces.
1. When using `Minishift` to spin up a local Kubernetes cluster, login in as `admin` to create the default namespace or
use `current` or `existing` namespace configured using properties `namespace.use.current` and `namespace.use.existing`
respectively if logged in as any other user.
2. In case of remote cluster use `current` or `existing` namespace for the user authenticated by token using property
`cube.auth.token` or by username and password using properties `cube.username` and `cube.password` respectively. The
properties can be set in `arquillian.xml` as shown in the snippet below or as system properties with the latter taking precedence.
[source, xml]
.arquillian.xml
----
existing-namespace
https://api.yourcluster.openshift.com
token
username
password
====
### 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.
#### 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. 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.
3. 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 comma-separated list of URLs.
4. 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).
(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)
[IMPORTANT]
====
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 adding the
username and password and 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/
====
=== 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 be ready, but also to be usable/connectable.
=== 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).
=== 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 testing 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:
[source, java]
.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
http://assertj.org[assert4j] for performing assertions on top of the Kubernetes model.
Also, you can gather control of what to deploy, when to deploy or when to wait for resources readiness.
To control you need to inject into test the `org.arquillian.cube.kubernetes.impl.KubernetesAssistant`.
[source, java]
----
include::../kubernetes/ftest-kubernetes-assistant/src/test/java/org/arquillian/cube/kubernetes/assistant/HelloWorldKubernetesAssistantTest.java[tag=k8_assistant_example]
----
<1> Sets the application name where everything is deployed.
<2> You can get the url of the deployed service on the cluster.
The next example is intended to show how you can inject a resource by id.
[source, java]
.ResourceByIdTest.java
----
@RunWith(Arquillian.class)
public class ResourceByIdTest {
@ArquillianResource
@Named("my-service")
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 is intended to show how you can inject a resource from secondary namespace.
[source, java]
.ResourceByIdInSecondaryNamespaceTest.java
----
@RunWith(Arquillian.class)
public class ResourceByIdInSecondaryNamespaceTest {
@ArquillianResource
@Named(value = "my-service", namespace = "my-predefined-namespace")
Service service;
@ArquillianResource
@Named(value = "my-pod", namespace = "my-predefined-namespace")
Pod pod;
@ArquillianResource
@Named(value = "my-contoller", namespace = "my-predefined-namespace")
ReplicationController controller;
@Test
public void testStuff() throws Exception {
//Do stuff...
}
}
----
The next example shows how to inject a resource filtering by label.
[source, java]
.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.
[source, java]
.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.
[source, java]
.OpenshiftExample.java
----
public class HelloWorldTest {
@Named("hello-openshift-service")
@PortForward
@ArquillianResource
Service service;
@Named("hello-openshift-service")
@PortForward
@ArquillianResource
URL url;
@Named(value = "another-openshift-service", namespace = "my-predefined-namespace")
@PortForward
@ArquillianResource
Service anotherService;
@Named(value = "another-openshift-service", namespace = "my-predefined-namespace")
@PortForward
@ArquillianResource
URL anotherUrl;
@Test
public void service_instances_should_not_be_null() throws Exception {
assertThat(service).isNotNull();
assertThat(anotherService).isNotNull();
}
@Test
public void testStuff() throws Exception {
//Do stuff...
//Modify something with request to url...
//Check results with request to anotherUrl...
}
}
----
In case of OpenShift, test can be enriched with `OpenShiftClient`.
[source, java]
.OpenshiftExample.java
----
public class HelloWorldTest {
@ArquillianResource
OpenShiftClient client;
}
----
Also you can gather control of what to deploy, when to deploy or when to wait for resources readiness.
To control you need to inject into test the `org.arquillian.cube.openshift.impl.client.OpenShiftAssistant`.
[source, java]
----
public class HelloWorldOpenShiftAssistantTest {
@ArquillianResource
OpenShiftAssistant openShiftAssistant;
@Test
public void should_apply_route_programmatically() throws IOException {
openShiftAssistant.deployApplication("hello-world", "hello-route.json"); // <1> <2>
final Optional route = openShiftAssistant.getRoute(); // <3>
}
}
----
<1> Sets the application name where everything is deployed
<2> Sets the resource to apply, in this case creation of an OpenShift `route`
<3> You can get the first defined route from cluster
Let's see how can you execute `oc` or `kubectl` commands as a part of your test.
[source, java]
.OpenshiftAndK8sExample.java
----
include::../openshift/ftest-oc-proxy/src/test/java/org/arquillian/cube/openshift/ftest/HelloWorldIT.java[tag=client_cli_execution]
----
=== OpenShift Integration with Graphene
Integration with Graphene allows for auto-resolution of the host and the context of the application deployed within the
OpenShift cluster by using the cluster's `route` definition for configuring Graphene.
In case, multiple routes are defined, user can select a `route`, by setting the route name as the host part in the URL
property for Graphene.
For example, as shown in the below snippet (1), a route named `hello-world` is selected for configuring the URL for
graphene.
[source, xml]
.arquillian.xml
----
http://hello-world:8080
----
If there is a single route or no route name is set, a default route from the definition is used for Graphene
configuration.
==== Example
Apart from adding arquillian and arquillian-cube-openshift, obviously you also need to add the dependencies for
arquillian-drone, selenium-bom and aquillian-graphene.
[source, xml]
.pom.xml
----
org.jboss.arquillian.extension
arquillian-drone-bom
2.0.0.Final
pom
import
org.jboss.arquillian.selenium
selenium-bom
2.53.1
pom
import
org.jboss.arquillian.extension
arquillian-drone-webdriver-depchain
2.0.0.Final
pom
test
org.jboss.arquillian.graphene
graphene-webdriver
2.1.0.Final
pom
test
org.jboss.arquillian.graphene
graphene-webdriver-impl
2.1.0.Final
test
----
You can see the example using Graphene at
https://github.com/arquillian/arquillian-cube/tree/master/openshift/ftest-openshift-graphene .
Also, you can learn more about Graphene at http://arquillian.org/guides/functional_testing_using_graphene/ .
=== OpenShift Integration with Rest-Assured
Integration with Rest-Assured allows for auto-resolution of the base URI of the application deployed within the
OpenShift cluster by using `OpenShift Route` definition for configuring Rest-Assured.
==== Configuration
You can configure a specific base URI using the `baseUri` property from restassured configuration. In this case, the
hostname is going to be resolved as OpenShift route name, and if there is no route with that name, then the base URI is
treated as is. For example:
[source, xml]
.arquillian.xml
----
include::../openshift/ftest-openshift-restassured/src/test/resources/arquillian.xml[tag=restassured_configuration]
----
As shown in the above snippet example (1), this integration will try to find an OpenShift route with name hello-world
and inject its IP. If there is no route with that name, then the base URI field is considered to be the final base URI.
If however, no specific base URI is configured, a default route from the definition is used for rest-assured
configuration.
==== Dependency
To use Arquillian Cube OpenShift RestAssured integration you only need to add as dependency.
[source, xml]
.pom.xml
----
org.arquillian.cube
arquillian-cube-openshift-restassured
test
----
==== Example
After setting the dependencies for OpenShift and OpenShift-RestAssured and configuring the extensions, you are all set
to write your Arquillian Cube test as :
[source, java]
.HelloOpenShiftRestAssuredIT.java
----
include::../openshift/ftest-openshift-restassured/src/test/java/org/arquillian/openshift/restassured/HelloOpenShiftRestAssuredIT.java[tag=openshift_restassured_example]
----
Notice that no _ip_ nor _port_ configuration are required since everything is managed and configured by Cube.
You can see the full example using OpenShift and Rest-Assured at
https://github.com/arquillian/arquillian-cube/tree/master/openshift/ftest-openshift-restassured .
=== Istio in Kubernetes/OpenShift
Arquillian Cube also support Istio, so you can apply Istio resources before executing tests.
As it happens with Arquillian Kubernetes/OpenShift integration, the integration is provided as annotation and as assistant.
==== Dependency
To use Istio integration you just need to add next dependency.
[source, xml]
.pom.xml
----
include::../pom.xml[indent=0, tag=istio_dependency]
----
TIP: Same dependency can be used for OpenShift
==== `@IstioResource`
You can define at a class or method level which Istio resource to apply before any test class or method execution. Those resources will be unregistered after all corresponding test class or methods are executed.
The location of Istio Resource can start with `http(s):` or `file:` which then the value is treated as `URL.
If location is prefixed with `classpath:`, then the resource is considered to be located at classpath.
If it is not prefixed, then the resource is considered to be the content text.
For example: `@IstioResource("classpath:istio_route_rule.yaml")`
The value also supports like `${property:defaultValue}` where `property is resolved against system property, if not set then environment variable, and if not set the default value (if specified) is returned.
[IMPORTANT]
====
When applying any resource, Istio notifies that the resource has been correctly registered, but this does not mean that all the configuration has been populated across all the cluster.
This means that when test method is executed, all Istio elements might not be updated yet.
At this time, Istio does not cover to query its state, so the only thing you can do is either sleeping some amount of time before executing the test or create some polling against a service that should be reachable after applying the rule.
<> provides some helper methd based on `awaitility` and `OkHttp`.
====
==== `@RestoreIstioResource`
Same as `@IstioResource`, there is a `@RestoreIstioResource` which is applied after execution (test class or test method depending on the scope).
==== Istio Assistant
You can register and unregister Istio resources programmatically using `IstioAssistant`.
Notice that both annotations and assistant approaches can be mixed in the same test.
[source, java]
----
include::../openshift/ftest-istio-openshift/src/test/java/org/arquillian/cube/openshift/standalone/ReviewsIT.java[indent=0, tag=istio_assistant]
----
The assistant provides you `deployIstioResources` and `undeployIstioResources` to deploy and undeploy Istio resources.
Assistant also provides a helper method to poll a URL based on `awaitility` and `OkHttp`.
For example:
[source, java]
----
final Request request = new Request.Builder()
.url(url.toString() + "api/v1/products/0/reviews")
.addHeader("Cookie", "user=alex; Domain=" + url.getHost() +"; Path=/")
.build(); // <1>
istioAssistant.await(request, response -> "2.0.0".equals(response.header("version"))); // <2>
----
<1> Creates the request to reach version 2.0.0 of the service
<2> When all proxies are updated the request will finally get service 2 (instead of 1) and return the version in header
=== Arquillian Kubernetes and OpenShift Recipes
To help you get started with ease, listed below are specially curated examples for Kubernetes and OpenShift Extensions.
==== Example 1
Deploying a sample PHP Guestbook application with Redis on Kubernetes from the resource descriptor
manifest file and testing it using Arquillian Cube Extension for Kubernetes and Kubernetes custom assertions.
Source: https://github.com/arquillian-testing-microservices/kubernetes-deployment-testing[arquillian-testing-microservices/kubernetes-deployment-testing]
==== Example 2
Deploying a Wordpress and My SQL application to OpenShift from a Template file and testing it using Arquillian
Cube Extension for OpenShift and Fabric8 OpenShift Client.
Source: https://github.com/arquillian-testing-microservices/openshift-deployment-testing[arquillian-testing-microservices/openshift-deployment-testing]
==== Example 3
Building and deploying a sample SpringBoot GuestBook application with zero deployment configuration using
https://maven.fabric8.io/[Fabric8 Maven Plugin] and
https://github.com/arquillian/arquillian-cube[Arquillian Cube Extension].
Fabric8 Maven Plugin aids in building Docker images and creating Kubernetes and OpenShift resource
descriptors for the application that allows for a quick ramp-up with some opinionated defaults and Arquillian
Cube Extension deploys the application from the generated resource descriptors and then executes deployment tests.
Source: https://github.com/arquillian-testing-microservices/zero-config-deployment-test[arquillian-testing-microservices/zero-config-deployment-test]
=== 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.