Skip to content

6. Openshift

Lukas Lowinger edited this page Mar 2, 2022 · 3 revisions

Requirements

  • OC CLI installed
  • Be connected to a running OpenShift instance - oc login ...

Verified Environments:

  • OCP 4.6+

Usage

Required Maven dependency:

<dependency>
    <groupId>io.quarkus.qe</groupId>
    <artifactId>quarkus-test-openshift</artifactId>
    <scope>test</scope>
</dependency>

And now, we can write also scenarios to be run in OpenShift by adding the @OpenShiftScenario annotation:

@OpenShiftScenario
public class OpenShiftPingPongResourceIT {

    @QuarkusApplication
    static final RestService app = new RestService();

    @Test
    public void shouldPingPongWorks() {
        app.given().get("/ping").then().statusCode(HttpStatus.SC_OK).body(is("ping"));
        app.given().get("/pong").then().statusCode(HttpStatus.SC_OK).body(is("pong"));
    }
}

We can reuse all the scenarios to be run on Baremetal and OpenShift by extending scenarios, for example having written the scenario on baremetal:

@QuarkusScenario
public class PingPongResourceIT {

    @QuarkusApplication
    static final RestService app = new RestService();

    @Test
    public void shouldPingPongWorks() {
        app.given().get("/ping").then().statusCode(HttpStatus.SC_OK).body(is("ping"));
        app.given().get("/pong").then().statusCode(HttpStatus.SC_OK).body(is("pong"));
    }
}

We can create a new scenario called OpenShiftPingPongResourceIT as:

@OpenShiftScenario
public class OpenShiftPingPongResourceIT extends PingPongResourceIT {
}

Deployment Strategies

(Default) Using S2i: From Binary

This strategy will build the Quarkus app artifacts locally and push it into OpenShift to generate the image that will be deployed.

Example:

@OpenShiftScenario // or @OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.Build)
public class OpenShiftPingPongResourceIT {
    @QuarkusApplication(classes = PingResource.class)
    static final RestService pingApp = new RestService();

    @Test
    public void shouldPingWorks() {
        pingApp.given().get("/ping").then().statusCode(HttpStatus.SC_OK).body(is("ping"));
        pingApp.given().get("/pong").then().statusCode(HttpStatus.SC_NOT_FOUND);
    }
}

The default template used by this strategy can be overwritten using the property ts.global.openshift.template.

OpenShift Extension

This strategy will delegate the deployment into the Quarkus OpenShift extension, so it will trigger a Maven command to run it.

Example:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
public class OpenShiftPingPongResourceIT {
    // ...
}

In order to use this strategy, you need to add this Maven profile into the pom.xml:

<profile>
    <id>deploy-to-openshift-using-extension</id>
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-openshift</artifactId>
        </dependency>
    </dependencies>
</profile>

| Important note: This strategy does not support custom sources to be selected, this means that the whole Maven module will be deployed. Therefore, if we have:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
public class OpenShiftUsingExtensionPingPongResourceIT {
    @QuarkusApplication(classes = PingResource.class)
    static final RestService pingPongApp = new RestService();
    
    // ...
}

The test case will fail saying that this is not supported using the Using OpenShift strategy.

OpenShift Extension with external app

It is possible to combine this strategy with @GitRepositoryQuarkusApplication as follows:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtension)
public class OpenShiftExtensionQuickstartUsingDefaultsIT {

    @GitRepositoryQuarkusApplication(repo = "https://github.com/apache/camel-quarkus-examples.git", contextDir = "file-bindy-ftp", mavenArgs = "-Dopenshift")
    static final RestService app = new RestService();
}

OpenShift Extension and Using Docker Build

This is an extension of the OpenShift Extension previous deployment strategy. The only difference is that a Docker build strategy will be used:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtensionAndDockerBuildStrategy)
public class OpenShiftUsingExtensionPingPongResourceIT {
    @QuarkusApplication(classes = PingResource.class)
    static final RestService pingPongApp = new RestService();
    
    // ...
}

The same limitations as in OpenShift Extension strategy apply here too.

OpenShift Extension and Using Docker Build with external app

It is possible to combine this strategy with @GitRepositoryQuarkusApplication as follows:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingOpenShiftExtensionAndDockerBuildStrategy)
public class OpenShiftExtensionUsingDockerBuildStrategyQuickstartUsingDefaultsIT {

    @GitRepositoryQuarkusApplication(repo = "https://github.com/apache/camel-quarkus-examples.git", contextDir = "file-bindy-ftp", mavenArgs = "-Dopenshift")
    static final RestService app = new RestService();
}

Using S2i: From Source

This strategy utilises source S2I process described by the Quarkus product documentation:

oc import-image --confirm ubi8/openjdk-11 --from=registry.access.redhat.com/ubi8/openjdk-11
oc new-app ubi8/openjdk-11 <git_path> --context-dir=<context_dir> --name=<project_name>

The application's git repository, ref, context dir and Quarkus version are all specified in @QuarkusApplication annotation.

Example:

@OpenShiftScenario
public class OpenShiftS2iQuickstartIT {

    @GitRepositoryQuarkusApplication(repo = "https://github.com/quarkusio/quarkus-quickstarts.git", contextDir = "getting-started")
    static final RestService app = new RestService();
    //

This scenario will work for JVM and Native builds. In order to manage the base image in use, the framework will use the standard Quarkus properties in:

  • For JVM: quarkus.openshift.base-jvm-image. Default is registry.access.redhat.com/ubi8/openjdk-11:latest.
  • For Native: ts.global.s2i.openshift.base-native-image. Default is quay.io/quarkus/ubi-quarkus-native-s2i:21.2-java11.

The way these properties are up to users. In the examples, we supply this configuration in the pom.xml as part of system properties (in the Maven failsafe plugin). But we can provide a custom property by service in the test.properties file. For further information about how to customise the properties, go to the Configuration section.

It's important to note that, by default, OpenShift will build the application's source code using the Red Hat maven repository https://maven.repository.redhat.com/ga/. However, some applications might require some dependencies from other remote Maven repositories. In order to allow us to add another remote Maven repository, you can use -Dts.global.s2i.maven.remote.repository=http://host:port/repo/name. If you only want to configure different maven repositories by service, you can do it by replacing global to the service name, for example: -Dts.pingPong.s2i.maven.remote.repository=....

The test framework will automatically load a custom maven settings with the provided maven remote repository. But if you're using a custom template, all you need to do is to configure the settings-mvn config map and the Maven args as follows:

apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  name: myApp
spec:
  source:
    git:
      uri: https://github.com/repo/name.git
    type: Git
    configMaps:
    - configMap:
        name: settings-mvn
      destinationDir: "/configuration"
  strategy:
    type: Source
    sourceStrategy:
      env:
      - name: MAVEN_ARGS
        value: -s /configuration/settings.xml
      // ...

The default template used by this strategy can be overwritten using the property ts.global.openshift.template.

Container Registry

This strategy will build the image locally and push it to an intermediary container registry (provided by a system property). Then, the image will be pulled from the container registry in OpenShift.

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingContainerRegistry)
public class OpenShiftUsingExtensionPingPongResourceIT {
    // ...
}

When running these tests, the container registry must be supplied as a system property:

mvn clean verify -Dts.container.registry-url=quay.io/<your username>

These tests can be disabled if the above system property is not set using the @DisabledIfNotContainerRegistry annotation:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingContainerRegistry)
@DisabledIfNotContainerRegistry
public class OpenShiftUsingExtensionPingPongResourceIT {
    // ...
}

The default template used by this strategy can be overwritten using the property ts.global.openshift.template.

Container Registry with external app

It is possible to combine this strategy with @GitRepositoryQuarkusApplication as follows:

@OpenShiftScenario(deployment = OpenShiftDeploymentStrategy.UsingContainerRegistry)
public class OpenShiftContainerRegistryQuickstartUsingDefaultsIT {
    
    @GitRepositoryQuarkusApplication(repo = "https://github.com/quarkusio/quarkus-quickstarts.git", contextDir = "getting-started")
    static final RestService app = new RestService();
}

Interact with the OpenShift Client directly

We can inject the OpenShift client to interact with OpenShift. This can be useful to cope with more complex scenarios like scale up/down services.

import io.quarkus.test.bootstrap.inject.OpenShiftClient;
import io.quarkus.test.scenarios.OpenShiftScenario;

@OpenShiftScenario
public class OpenShiftGreetingResourceIT extends GreetingResourceIT {
    @Test
    public void shouldInjectOpenShiftClient(OpenShiftClient client) {
        // ...
        client.scaleTo(app, 2);
    }
}

Another option is by injecting the client directly to the test class using the @Inject annotation:

import io.quarkus.test.bootstrap.inject.OpenShiftClient;
import io.quarkus.test.scenarios.OpenShiftScenario;

@OpenShiftScenario
public class OpenShiftGreetingResourceIT extends GreetingResourceIT {

    @Inject
    static OpenShiftClient client;
    
    @Test
    public void shouldInjectOpenShiftClient() {
        // ...
        client.scaleTo(app, 2);
    }
}

| Note that the injection is only supported to static fields.

Disable ephemeral namespaces

The framework will create a project for every scenario and delete it once the scenario is finished. However, sometimes due to cluster restrictions we're not allowed to create namespaces. In those cases, you can disable ephemeral namespaces and run all your tests in your current namespace.

-Dts.openshift.ephemeral.namespaces.enabled=false

Use custom templates for Containers

Sometimes deploying a third party into OpenShift involves some complex configuration that is not required when deploying on bare metal. For these scenarios, we allow to provide a custom template via test.properties:

ts.consul.openshift.template=/yourtemplate.yaml

| Note the custom template must contain ONLY ONE deployment config.

Moreover, if the service that is exposing the port we want to target is named differently to our service, we can provide the service name via:

ts.consul.openshift.service=consul-http-service

Use internal service routing for Containers

What about if we want to use the internal service as route (not the exposed route), we can set this behaviour by enabling the property ts.<MY_SERVICE>.openshift.use-internal-service-as-url:

ts.consul.openshift.use-internal-service-as-url=true

Enable/Disable Project Deletion on Failures

By default, the framework will always delete the OpenShift project and, sometimes, it's useful to not delete the OpenShift project on failures to troubleshooting purposes. For disabling the deletion, we need to run the test using:

mvn clean verify -Dts.openshift.delete.project.after.all=false

Print useful information on errors

The test framework will print the project status, events and pod logs when a test fails. This functionality is enabled by default, however it can be disabled using the property -Dts.openshift.print.info.on.error=false.

Operators

The OpenShift scenarios support Operator based test cases. There are two ways to deal with Operators:

  • Installing the Operators as part of the OpenShiftScenario:
@OpenShiftScenario(
        operators = @Operator(name = "strimzi-kafka-operator")
)
public class StrimziOperatorKafkaWithoutRegistryMessagingIT {
    // We can now use the new Operator CRDs manually
}
  • Installing and managing Custom Resource Definitions as services

First, we need to create our Custom Resource YAML file, for example, for Kafka:

apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
  name: kafka-instance
spec:
  ...

Now, we can create an OperatorService to load this YAML as part of an Operator installation:

@OpenShiftScenario
public class OperatorExampleIT {

    @Operator(name = "my-operator", source = "...")
    static final OperatorService operator = new OperatorService().withCrd("kafka-instance", "/my-crd.yaml");

    @QuarkusApplication
    static final RestService app = new RestService();

    // ...
}

The framework will install the operator and load the YAML file by you.

Note that the framework will wait for the operator to be installed before loading the CRD yaml files, but will not wait for the CRDs to be ready. If you are working with CRDs that update conditions, then we can ease this for you by providing the custom resource definition:

@Version("v1beta2")
@Group("kafka.strimzi.io")
@Kind("Kafka")
public class KafkaInstanceCustomResource
        extends CustomResource<CustomResourceSpec, CustomResourceStatus>
        implements Namespaced {
}

And then registering the CRD with this type:

@OpenShiftScenario
public class OperatorExampleIT {

    @Operator(name = "my-operator", source = "...")
    static final OperatorService operator = new OperatorService().withCrd("kafka-instance", "/my-crd.yaml", KafkaInstanceCustomResource.class);

    @QuarkusApplication
    static final RestService app = new RestService();

    // ...
}

Now, the framework will wait for the operator to be installed and the custom resource named kafka-instance to be with a condition "Ready" as "True".