Cloud test suite for WildFly
You need a local Kubernetes cluster (minikube) with a local Docker registry at localhost:5000. CI uses Docker; instructions for Podman are also provided below.
-
Start minikube:
minikube start --driver=docker --container-runtime=containerd --memory='4gb' --cpus='2'
-
Start a local Docker registry:
docker run -d -p 5000:5000 --restart=always --name local-registry \ -e REGISTRY_STORAGE_DELETE_ENABLED=true \ registry:2
-
Connect the registry to minikube's network so pods can pull images:
MINIKUBE_NETWORK=$(docker inspect minikube --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}') docker network connect "$MINIKUBE_NETWORK" local-registry
-
Configure containerd inside minikube to resolve
localhost:5000via the registry container's IP. Without this, pods that referencelocalhost:5000/...images would look on the node's own loopback and fail withImagePullBackOff:REGISTRY_IP=$(docker inspect local-registry \ --format="{{.NetworkSettings.Networks.${MINIKUBE_NETWORK}.IPAddress}}") minikube ssh "sudo mkdir -p /etc/containerd/certs.d/localhost:5000" minikube ssh "printf '[host.\"http://${REGISTRY_IP}:5000\"]\n capabilities = [\"pull\", \"resolve\"]\n' \ | sudo tee /etc/containerd/certs.d/localhost:5000/hosts.toml" minikube ssh "sudo systemctl restart containerd"
Wait a few seconds for containerd to restart before running tests.
-
Verify the setup:
# Registry is accessible from the host curl -s http://localhost:5000/v2/_catalog # Registry is accessible from inside minikube minikube ssh "curl -f http://${REGISTRY_IP}:5000/v2/"
These instructions use a standalone registry container, the same approach as the Docker instructions above. The minikube registry addon does not work reliably with Podman on macOS/Windows because --network=host maps to the Podman VM's network rather than the host machine's.
-
Platform-specific prerequisites:
macOS/Windows only: You need a running Podman machine with at least 8 GB of memory and 4 CPUs (minikube, the registry, and test workloads all share these resources). If you don't have one yet:
podman machine init --memory 8192 --cpus 4 podman machine start
Next, configure
localhost:5000as an insecure (HTTP) registry inside the Podman machine. This must be done before starting minikube. Check whether the configuration already exists:podman machine ssh --username root cat /etc/containers/registries.conf
If the output does not contain an entry for
localhost:5000, add one:podman machine ssh --username root tee -a /etc/containers/registries.conf <<'EOF' [[registry]] location="localhost:5000" insecure=true EOF
Verify it was written:
podman machine ssh --username root cat /etc/containers/registries.conf
Then restart the Podman machine so the configuration takes effect:
podman machine stop && podman machine startNote: On macOS, AirPlay Receiver sometimes binds to port 5000. If you get connection errors later, disable it in System Settings > General > AirDrop & Handoff > AirPlay Receiver.
Linux only: Enable the podman socket and set
DOCKER_HOSTso minikube can find it:systemctl --user enable --now podman.socket export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
You may also need to register
localhost:5000as an insecure registry. Add the following to/etc/containers/registries.conf(recent Podman versions allow localhost by default, but some distributions require this):[[registry]] location="localhost:5000" insecure=true
-
Start minikube with the podman driver:
minikube start --driver=podman --container-runtime=containerd --memory='4gb' --cpus='2'
-
Start a local registry:
podman run -d -p 5000:5000 --rm --name local-registry \ -e REGISTRY_STORAGE_DELETE_ENABLED=true \ registry:2
-
Connect the registry to minikube's network so pods can pull images:
MINIKUBE_NETWORK=$(podman inspect minikube --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}') podman network connect "$MINIKUBE_NETWORK" local-registry
-
Configure containerd inside minikube to resolve
localhost:5000via the registry container's IP. Without this, pods that referencelocalhost:5000/...images would look on the node's own loopback and fail withImagePullBackOff:REGISTRY_IP=$(podman inspect local-registry \ --format="{{.NetworkSettings.Networks.${MINIKUBE_NETWORK}.IPAddress}}") minikube ssh "sudo mkdir -p /etc/containerd/certs.d/localhost:5000" minikube ssh "printf '[host.\"http://${REGISTRY_IP}:5000\"]\n capabilities = [\"pull\", \"resolve\"]\n' \ | sudo tee /etc/containerd/certs.d/localhost:5000/hosts.toml" minikube ssh "sudo systemctl restart containerd"
Wait a few seconds for containerd to restart before running tests.
-
Verify the setup:
# Registry is accessible from the host curl -s http://localhost:5000/v2/_catalog # Registry is accessible from inside minikube minikube ssh "curl -f http://${REGISTRY_IP}:5000/v2/"
The WildFly Maven Plugin uses podman by default for building container images, so no -Dwildfly.image.container-runtime override is needed when running the tests with Podman.
Known limitation: The OIDC (elytron-oidc-client) test uses NodePort services to handle the browser-like login flow between WildFly and Keycloak. On macOS with Podman, the minikube node IP (
minikube ip) is inside the Podman VM and not routable from the host, so the test will fail with a connection timeout. This test passes on CI (Docker on Linux) where the minikube IP is directly reachable.
The kubernetes-tests profile is active by default, and runs the tests tagged with @Tag(WildflyTags.KUBERNETES). These tests target Kubernetes, running on Minikube as outlined above.
mvn clean verify -Pkubernetes-testsBy default, the WildFly Maven Plugin uses podman to build container images. To use docker instead:
mvn clean verify -Pkubernetes-tests -Dwildfly.image.container-runtime=dockerOn CI, set CLOUD_TESTS_CI=1 to enable registry cleanup between tests (reduces disk usage):
CLOUD_TESTS_CI=1 mvn clean verify -Pkubernetes-tests -Dwildfly.image.container-runtime=dockerIf a test fails, pod logs and server configuration are automatically dumped to the console.
Each test module is self-contained:
- Application code in
src/main/java— a plain WAR application (JAX-RS, Servlet, etc.) - Deployment YAML in
src/main/kubernetes/wildfly-deployment.yml— static Kubernetes Deployment + Service - WildFly Maven Plugin in the module's
pom.xml— provisions the server and builds a container image viawildfly:image - CLI scripts (optional) in
src/main/cli-script/init.cli— JBoss CLI commands run during server provisioning - Test code in
src/test/java— a JUnit 5 integration test using@WildFlyKubernetesIntegrationTest - Test overrides (optional) in
src/test/resources/overrides/— override script and prerequisite Kubernetes YAMLs
The test lifecycle is:
mvn packagebuilds the WAR, provisions a WildFly server with the configured Galleon layers, and builds a container image- The JUnit 5 extension (
WildFlyKubernetesExtension) calls.github/scripts/setup-test.shin@BeforeAll, which tags/pushes the image to the local registry and applies the deployment YAML - Tests run against the deployed application using port-forwarding
- The extension calls
.github/scripts/teardown-test.shin@AfterAllto clean up
To add a test, create a new Maven module under tests/. We use a few levels of folders to group tests by area of functionality.
We use the Failsafe plugin to run these tests:
src/main/javaandsrc/main/webappcontain the application being testedsrc/test/javacontains the test, which works as a client test
A plain JAX-RS or Servlet application — no special annotations needed beyond the standard ones:
@ApplicationPath("")
public class MyApp extends Application {
}Create src/main/kubernetes/wildfly-deployment.yml with a Deployment and Service. The key requirements:
- The
app.kubernetes.io/namelabel must match the<name>in the WildFly Maven Plugin config - The image must be
localhost:5000/<artifactId>:latest(the WildFly Maven Plugin names images after the Maven artifactId) - Include ports
8080(http) and9990(admin)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-test
labels:
app.kubernetes.io/name: my-test
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: my-test
template:
metadata:
labels:
app.kubernetes.io/name: my-test
spec:
containers:
- name: my-test
image: localhost:5000/wildfly-cloud-tests-my-test:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
- name: admin
containerPort: 9990
---
apiVersion: v1
kind: Service
metadata:
name: my-test
spec:
selector:
app.kubernetes.io/name: my-test
ports:
- name: http
port: 8080
targetPort: 8080
- name: admin
port: 9990
targetPort: 9990Add to the module's pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<name>ROOT.war</name>
<container-runtime>${wildfly.image.container-runtime}</container-runtime>
<feature-packs>
<feature-pack>
<location>${server.feature.pack.gav}</location>
</feature-pack>
<feature-pack>
<location>${cloud.feature.pack.gav}</location>
</feature-pack>
</feature-packs>
<layers>
<layer>${cloud.feature.pack.default.config.layer}</layer>
<!-- Add additional layers as needed -->
</layers>
<galleon-options>
<jboss-fork-embedded>${plugin.fork.embedded}</jboss-fork-embedded>
</galleon-options>
</configuration>
<executions>
<execution>
<id>provision-server</id>
<phase>package</phase>
<goals><goal>package</goal></goals>
</execution>
<execution>
<id>build-image</id>
<phase>package</phase>
<goals><goal>image</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>The <name> must match the application name used in the deployment YAML and labels.
@WildFlyKubernetesIntegrationTest
public class MyTestIT extends WildFlyCloudTestCase {
@Test
public void smokeTest() throws Exception {
getHelper().doWithWebPortForward("", url -> {
Response r = RestAssured.get(url);
assertEquals(200, r.getStatusCode());
return null;
});
}
}The @WildFlyKubernetesIntegrationTest annotation includes @Tag("Kubernetes"), so no separate @Tag is needed.
WildFlyCloudTestCase provides getHelper() which returns a TestHelper with methods for:
doWithWebPortForward(path, action)— port-forward to the pod and execute an actionexecuteCLICommands(commands...)— run JBoss CLI commands in the podreadFile(path)— read a file from the podrunCommand(command, expectOutput)— run a bash command in the podkubectl()— access theKubectlHelperfor direct kubectl operations
If your test needs additional Kubernetes resources (databases, message brokers, operators), create an override script at <module-path>/src/test/resources/overrides/overridable-functions.sh. Place any prerequisite YAML files alongside the override script in the same overrides/ directory.
The available functions to override are defined in .github/scripts/overridable-functions.sh:
applicationName(modulePath)— the application name (default: last directory component)namespace(appName)— the namespace to deploy into (default: current context namespace)installPrerequisites(appName)— install additional resources before the appcleanPrerequisites(appName)— clean up additional resources after the testgetDeploymentYaml()— path to the deployment YAML (default:src/main/kubernetes/wildfly-deployment.yml)waitForReadiness(appName)— wait for the deployment to be readypreprocessYaml()— filter to transform the deployment YAML (e.g., placeholder substitution)containerCommand()— the container runtime command (default: auto-detect docker/podman)
Example override for a test that needs a PostgreSQL database:
function installPrerequisites() {
local app="${1}"
kubectl apply -f src/test/resources/overrides/kubernetes.yml
kubectl wait --for=condition=Available deployment/postgres --timeout=120s
}
function cleanPrerequisites() {
local app="${1}"
kubectl delete -f src/test/resources/overrides/kubernetes.yml --ignore-not-found=true
}The tests in the tests/manual folder will not run automatically, as they need external
systems to be set up before running. See the README for each test for how to run them.