Skip to content

Commit 60edc30

Browse files
committed
Wait for Docker health checks before proceeding
Add waitForFederatedEnvironment(), waitForHealthy(), and isContainerHealthy() to block until all Openfire instances in the federated environment report healthy via Docker's health check mechanism, up to a configurable timeout.
1 parent a9d7174 commit 60edc30

File tree

1 file changed

+74
-3
lines changed

1 file changed

+74
-3
lines changed

src/test/java/org/igniterealtime/openfire/integration/federation/FederatedTestEnvironment.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.nio.file.Path;
1010
import java.nio.file.StandardCopyOption;
1111
import java.time.Duration;
12+
import java.time.Instant;
1213
import java.time.temporal.ChronoUnit;
1314

1415
/**
@@ -33,7 +34,7 @@ public class FederatedTestEnvironment {
3334
private static boolean initialized = false;
3435

3536
// Time to wait for services to start up completely
36-
private static final Duration STARTUP_WAIT = Duration.of(30, ChronoUnit.SECONDS);
37+
private static final Duration STARTUP_WAIT = Duration.of(3, ChronoUnit.MINUTES);
3738

3839
// Port configuration for each XMPP server
3940
public static final int XMPP1_PORT = 5221; // First server client port
@@ -70,8 +71,7 @@ public static synchronized void start() throws Exception {
7071
if (!initialized) {
7172
setupSqlOverlay();
7273
startFederatedEnvironment();
73-
logger.info("Waiting {} seconds for servers to initialize...", STARTUP_WAIT.toSeconds());
74-
Thread.sleep(STARTUP_WAIT.toMillis());
74+
waitForFederatedEnvironment();
7575
initialized = true;
7676
// Register shutdown hook to ensure cleanup happens even if tests fail
7777
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@@ -175,6 +175,77 @@ private static void startFederatedEnvironment() throws IOException, InterruptedE
175175
}
176176
}
177177

178+
/**
179+
* Waits for the federated environment to have fully initialized and all servers to be healthy.
180+
* <p>
181+
* Blocks until all Openfire instances in the federated environment report a healthy status,
182+
* or until {@link #STARTUP_WAIT} has elapsed.
183+
*
184+
* @throws IllegalStateException if any server does not become healthy within the allotted time
185+
* @throws InterruptedException if the thread is interrupted while waiting
186+
* @throws IOException if an error occurs communicating with the Docker daemon
187+
*/
188+
private static void waitForFederatedEnvironment() throws IllegalStateException, InterruptedException, IOException
189+
{
190+
logger.info("Waiting up to {} seconds for servers to initialize...", STARTUP_WAIT.toSeconds());
191+
final Instant deadline = Instant.now().plus(STARTUP_WAIT);
192+
waitForHealthy("openfire", "xmpp1", deadline);
193+
waitForHealthy("openfire", "xmpp2", deadline);
194+
}
195+
196+
/**
197+
* Blocks until a specific Docker container reports a healthy status, or the deadline is reached.
198+
*
199+
* The container is identified by the standard Compose naming convention: {@code <project>-<service>-1}.
200+
*
201+
* Health status is polled using the Docker inspect command.
202+
*
203+
* @param project the Docker Compose project name
204+
* @param service the service name within the Compose project
205+
* @param deadline the point in time after which the wait is abandoned
206+
* @throws IllegalStateException if the container does not become healthy before the deadline
207+
* @throws InterruptedException if the thread is interrupted while waiting
208+
* @throws IOException if an error occurs communicating with the Docker daemon
209+
*/
210+
private static void waitForHealthy(String project, String service, Instant deadline) throws IOException, InterruptedException
211+
{
212+
System.out.print("Waiting for " + service + "...");
213+
while (Instant.now().isBefore(deadline)) {
214+
if (isContainerHealthy(project, service)) {
215+
System.out.println(" healthy.");
216+
return;
217+
}
218+
Thread.sleep(1_000);
219+
System.out.print(".");
220+
}
221+
throw new IllegalStateException("Timed out waiting for " + service + " to become healthy");
222+
}
223+
224+
/**
225+
* Checks whether a Docker container is currently reporting a healthy status.
226+
*
227+
* Invokes {@code docker inspect} to retrieve the container's health status. Returns {@code false} for any status
228+
* other than {@code healthy}, including {@code starting}, {@code unhealthy}, or if the container cannot be found.
229+
*
230+
* @param project the Docker Compose project name
231+
* @param service the service name within the Compose project
232+
* @return {@code true} if the container's health status is {@code healthy}, {@code false} otherwise
233+
* @throws InterruptedException if the thread is interrupted while waiting for the {@code docker inspect} process to complete
234+
* @throws IOException if the {@code docker} executable cannot be found or an I/O error occurs
235+
*/
236+
private static boolean isContainerHealthy(String project, String service) throws IOException, InterruptedException
237+
{
238+
final String containerName = project + "-" + service + "-1";
239+
final ProcessBuilder pb = new ProcessBuilder("docker", "inspect", "--format", "{{.State.Health.Status}}", containerName);
240+
pb.redirectErrorStream(true);
241+
242+
final Process process = pb.start();
243+
final String output = new String(process.getInputStream().readAllBytes()).trim();
244+
process.waitFor();
245+
246+
return "healthy".equals(output);
247+
}
248+
178249
/**
179250
* Invokes the get_logs.sh script to export Openfire logs from the images.
180251
*

0 commit comments

Comments
 (0)