Comprehensive test suite for mod_cluster with WildFly/EAP workers and Undertow/httpd balancers.
This test suite uses:
- JUnit 5 for test framework
- AssertJ for soft assertions
- Testcontainers for container-based testing
- Creaper for WildFly/EAP management (clean, type-safe API)
- Dependency Injection pattern (no abstract base classes)
src/test/java/org/jboss/modcluster/test/
├── apps/ # Test application endpoints (e.g. WebSocket)
├── base/ # Core test infrastructure
│ ├── BalancerType.java # Balancer type enum
│ └── ModClusterTestExtension.java # JUnit 5 extension for DI
├── cli/ # CLI & management tests
│ ├── CliManagementTest.java
│ └── MultipleUndertowServerSupportTest.java
├── configuration/ # Configuration tests
│ ├── DynamicReconfTest.java
│ ├── InitialLoadTest.java
│ ├── SettingsTest.java
│ └── WorkerWithOneNotRespondingProxyTest.java
├── context/ # Context lifecycle tests
│ └── ContextLifecycleTest.java
├── failover/ # Failover scenarios
│ ├── AdvancedFailoverTest.java
│ ├── FailoverSettingsTest.java
│ ├── StickySessionTest.java
│ └── WebSocketsTest.java
├── ha/ # High availability & soak tests
│ ├── HighAvailabilityTest.java
│ └── SoakTest.java
├── loadbalancing/ # Load balancing tests
│ ├── LoadBalancingGroupFailoverTest.java
│ └── LoadMetricsTest.java
├── session/ # Session management tests
│ └── SessionManagementTest.java
├── ssl/ # SSL/TLS tests
│ ├── SslCrlTest.java
│ ├── SslFailoverTest.java
│ └── SslWorkerAuthenticationTest.java
└── utils/ # Utilities
├── BalancerContainer.java
├── WildFlyContainer.java
├── HttpClient.java
└── ...
- Java 17 or higher
- Maven 3.6+
- Docker or Podman
- WildFly or EAP ZIP distribution (optional, will use pre-built images as fallback)
-
Place your WildFly/EAP ZIP in the distributions directory:
cp ~/Downloads/wildfly-39.0.1.Final.zip distributions/ # or cp ~/Downloads/jboss-eap-8.0.0.zip distributions/
Alternatively, download WildFly from Maven Central:
mvn generate-test-resources -Pdownload-wildfly -Dwildfly.version=39.0.1.Final -DskipTests
-
Check prerequisites (optional):
./setup.sh
-
Run tests (Docker images are built automatically on first run):
mvn test
mvn test# Via system property
mvn test -Dwildfly.zip.path=/path/to/wildfly-31.0.1.Final.zip
# Via environment variable
export WILDFLY_ZIP_PATH=/path/to/jboss-eap-8.0.0.zip
mvn test# During setup
CONTAINER_JAVA_VERSION=17 ./setup.sh
# During tests
mvn test -Dcontainer.java.version=17
# Or combine both
CONTAINER_JAVA_VERSION=17 ./setup.sh
mvn test -Dcontainer.java.version=17mvn test -Phttpdor
mvn test -Dbalancer.type=httpdmvn test -Dtest=StickySessionTestmvn test -Dwildfly.version=31.0.1.FinalTests use dependency injection via JUnit 5 extensions:
@ExtendWith({ModClusterTestExtension.class, SoftAssertionsExtension.class})
public class MyTest {
@InjectSoftAssertions
private SoftAssertions softly;
@Test
public void testSomething(TestCluster cluster, HttpClient httpClient) throws Exception {
// Start workers
cluster.startWorkers(2);
// Get balancer URL
String url = cluster.getBalancer().getHttpUrl() + "/demo";
// Make requests
HttpResponse response = httpClient.get(url);
// Assertions
softly.assertThat(response.getStatusCode()).isEqualTo(200);
}
}TestCluster cluster- Provides access to balancer and workersHttpClient httpClient- HTTP client for making requestsBalancerContainer balancer- Direct balancer access@InjectSoftAssertions SoftAssertions softly- Soft assertions
// Start 1 worker
cluster.startWorkers(1);
WildFlyContainer worker = cluster.getWorker1();
// Start 2 workers
cluster.startWorkers(2);
WildFlyContainer worker1 = cluster.getWorker1();
WildFlyContainer worker2 = cluster.getWorker2();// Simple GET
HttpResponse response = httpClient.get(url);
// GET with session
HttpResponse response = httpClient.getWithSession(url, "JSESSIONID=" + sessionId);
// HTTPS request
HttpResponse response = httpClient.getHttps(httpsUrl);
// Test load distribution
Map<String, Integer> distribution = httpClient.testLoadDistribution(url, 100);WildFlyContainer worker = cluster.getWorker1();
String result = worker.executeCli("/subsystem=modcluster:read-resource");- CliManagementTest - CLI operations, configuration read/write, deployment status
- MultipleUndertowServerSupportTest - Multiple Undertow server instances
- AdvancedFailoverTest - Failover with active sessions, deterministic and graceful failover
- FailoverSettingsTest - Failover configuration options
- StickySessionTest - Session affinity across requests
- WebSocketsTest - WebSocket proxying and failover
- SslCrlTest - Certificate Revocation List validation
- SslFailoverTest - SSL with failover scenarios
- SslWorkerAuthenticationTest - Mutual SSL authentication
- LoadBalancingGroupFailoverTest - Load distribution and group failover
- LoadMetricsTest - Load metrics calculation and custom metrics
- DynamicReconfTest - Dynamic worker registration and reconfiguration
- InitialLoadTest - Initial load reporting
- SettingsTest - Configuration settings validation
- WorkerWithOneNotRespondingProxyTest - Proxy resilience
- ContextLifecycleTest - Context enable/disable, exclusion patterns, deployment registration
- SessionManagementTest - Session timeout, custom cookies, JVM routes
- HighAvailabilityTest - Hot standby, multiple balancers
- SoakTest - Long-running stability testing
This test suite aims for feature parity with noe-tests/modcluster (64 test files). The following table shows coverage status:
| Area | Implemented | Key Test Classes |
|---|---|---|
| CLI & Management | Yes | CliManagementTest, MultipleUndertowServerSupportTest |
| Sticky Sessions | Yes | StickySessionTest |
| Advanced Failover | Yes | AdvancedFailoverTest, FailoverSettingsTest |
| Load Balancing | Yes | LoadBalancingGroupFailoverTest, LoadMetricsTest |
| SSL/TLS | Yes | SslCrlTest, SslFailoverTest, SslWorkerAuthenticationTest |
| Dynamic Reconfiguration | Yes | DynamicReconfTest, SettingsTest |
| Context Lifecycle | Yes | ContextLifecycleTest |
| Session Management | Yes | SessionManagementTest |
| High Availability | Yes | HighAvailabilityTest |
| WebSockets | Yes | WebSocketsTest |
| Initial Load | Yes | InitialLoadTest |
| Soak/Stress Testing | Yes | SoakTest |
| Area | noe-tests Reference |
|---|---|
| AJP Protocol | ModClusterAJP.groovy |
| EJB over HTTP | EjbViaHttpTest.groovy |
| mod_proxy / mod_rewrite | ModProxyTest.groovy, ModRewriteTest.groovy |
| Bug-specific regressions | JBCS*, JBQA* test files |
The same WildFly/EAP ZIP is used for both workers AND the Undertow balancer - just with different configurations:
- Tests look for ZIP distributions in the
distributions/directory - If found, builds Docker images using
docker build(avoids Testcontainers large file limitations):- Checks if image already exists (reuses if available)
- If not, runs
docker builddirectly with the ZIP - Uses Red Hat UBI9 with OpenJDK by default (version auto-detected based on WildFly/EAP version); override with
-Dcontainer.base.image- WildFly 27+ / EAP 8+: Uses OpenJDK 17
- Extracts the ZIP inside the image
- Same image used for both workers and balancer (configuration differs at runtime)
- For workers: Starts with
standalone-ha.xml, connects to balancer - For Undertow balancer: Starts with
standalone-ha.xml, acts as load balancer (advertise enabled)
- If no ZIP is found, falls back to pre-built container images
Image naming: modcluster-test/wildfly-31-0-1-final:ubi9-openjdk-17
WildFly uses JGroups for worker-to-worker session replication. The default standalone-ha.xml uses UDP multicast for cluster discovery, which does not work in Docker/Podman networks. To solve this, WildFlyContainer automatically reconfigures JGroups at startup:
- Binds the private interface to
0.0.0.0(-bprivate 0.0.0.0) so JGroups TCP listens on the container's network interface instead of127.0.0.1 - Switches from UDP to the TCP stack and replaces MPING (multicast discovery) with TCPPING using container network aliases (
worker1[7600],worker2[7600], etc.)
This is transparent to the tests — JGroups handles internal session replication while mod_cluster handles balancer-to-worker communication via MCMP over HTTP. The two layers are independent.
Note: The reference noe-tests achieve the same result differently — they set
AS7_PRIVATE_IP_ADDRESSto a real IP and rely on native multicast, which works on bare metal/VM networks.
- Undertow balancer:
- With ZIP: Builds from your WildFly/EAP ZIP (same as workers)
- Without ZIP: Falls back to a pre-built image (placeholder:
quay.io/modcluster/mod_cluster-undertow:latest— does not exist yet, provide your own via-Dbalancer.undertow.image=) - Customizable via
-Dbalancer.undertow.image=
- httpd balancer:
- With httpd ZIP (
-Dhttpd.zip.path=): Builds from a pre-built httpd ZIP (e.g. JBCS). Auto-detects RHEL version from ZIP filename for the base image. - Without ZIP: Builds httpd from source and compiles mod_proxy_cluster modules (uses
fedora:42as base) - Pre-built image: Override with
-Dbalancer.httpd.image=to skip building entirely
- With httpd ZIP (
- System property:
-Dwildfly.zip.path=/path/to/wildfly.zip - Environment variable:
WILDFLY_ZIP_PATH=/path/to/wildfly.zip - Convention: First ZIP found in
distributions/directory - Fallback: Pre-built container images
Default fallback images (when no ZIP provided). The quay.io/modcluster/ images are placeholders that do not exist yet — provide a ZIP or override with your own images.
| Component | Default Image (placeholder) | Override |
|---|---|---|
| Undertow balancer | quay.io/modcluster/mod_cluster-undertow:latest |
-Dbalancer.undertow.image= |
| httpd balancer | quay.io/modcluster/mod_cluster-httpd:latest |
-Dbalancer.httpd.image= |
| WildFly workers | quay.io/wildfly/wildfly:<version> |
Provide a ZIP in distributions/ |
In practice, always provide a WildFly/EAP ZIP — the fallback images are not published.
When adding new tests:
- Choose appropriate package based on test category
- Use
@ExtendWith({ModClusterTestExtension.class, SoftAssertionsExtension.class}) - Inject
SoftAssertionsfor assertions - Inject
TestClusterandHttpClientas needed - Document test purpose in class javadoc
- Follow existing naming conventions
See CONTRIBUTING.adoc for detailed guidelines.
See LICENSE file for details.