From b6e84c8e2f117f256dff0169b25409680548280f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 9 Nov 2021 17:33:42 +0200 Subject: [PATCH] When launching a container via @QuarkusIntegrationTest make network available This is pretty much the same as we did in 21287, but it now works regardless of the presence of DevServices. The difference with containers launched by DevServices, is that in this case we now need to manage the creation and deletion of the network, whereas in the case of DevServices we got that for free by utilizing testcontainers Resolves: #19080 --- .../asciidoc/getting-started-testing.adoc | 41 +++++++++++++++++++ .../quarkus/test/common/ArtifactLauncher.java | 2 + .../DefaultDockerContainerLauncher.java | 27 ++++++++++++ .../test/junit/IntegrationTestUtil.java | 16 ++++++-- .../test/junit/NativeTestExtension.java | 5 +++ 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 9c9ba7b36afa3..6b83ee6a9092b 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1215,6 +1215,47 @@ As a test annotated with `@QuarkusIntegrationTest` tests the result of the build These tests will **not** work if run in the same phase as `@QuarkusTest` as Quarkus has not yet created the final artifact. ==== +=== Launching containers + +When `@QuarkusIntegrationTest` results in launching a container (because the application was built with `quarkus.container-image.build` set to `true`), the container is launched on a predictable container network. This facilitates writing integration tests that need to launch services to support the application. +This means that `@QuarkusIntegrationTest` works out of the box with containers launched via link:dev-services[Dev Services], but it also means that it enables using <> resources that launch additional containers. +This can be achieved by having your `QuarkusTestLifecycleManager` implement `io.quarkus.test.common.DevServicesContext.ContextAware`. A simple example could be the following: + +[source,java] +---- +import io.quarkus.test.common.DevServicesContext; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class CustomResource implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { + + private Optional containerNetworkId; + + @Override + public void setIntegrationTestContext(DevServicesContext context) { + containerNetworkId = context.containerNetworkId(); + } + + @Override + public Map start() { + // start a container making sure to call withNetworkMode() with the value of containerNetworkId if present + + // return a map containing the configuration the application needs to use the service + return new HashMap<>(); + } + + @Override + public void stop() { + // close container + } +} +---- + +`CustomResource` would be activated on a `@QuarkusIntegrationTest` using `@QuarkusTestResource` as is described in the corresponding section of this doc. + === Executing against a running application [WARNING] diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/ArtifactLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/ArtifactLauncher.java index 580a24bffcad3..8e7b41d46dd06 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/ArtifactLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/ArtifactLauncher.java @@ -38,6 +38,8 @@ interface DevServicesLaunchResult extends AutoCloseable { String networkId(); + boolean manageNetwork(); + void close(); } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java index ddc0a3a2e6a58..cd0e50dd00529 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java @@ -74,6 +74,33 @@ public void start() throws IOException { } } + if (devServicesLaunchResult.manageNetwork() && (devServicesLaunchResult.networkId() != null)) { + try { + int networkCreateResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) + .command(DOCKER_BINARY, "network", "create", devServicesLaunchResult.networkId()).start().waitFor(); + if (networkCreateResult > 0) { + throw new RuntimeException("Creating container network '" + devServicesLaunchResult.networkId() + + "' completed unsuccessfully"); + } + // do the cleanup in a shutdown hook because there might be more services (launched via QuarkusTestResourceLifecycleManager) connected to the network + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + try { + new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) + .command(DOCKER_BINARY, "network", "rm", devServicesLaunchResult.networkId()).start() + .waitFor(); + } catch (InterruptedException | IOException ignored) { + System.out.println( + "Unable to delete container network '" + devServicesLaunchResult.networkId() + "'"); + } + } + })); + } catch (InterruptedException e) { + throw new RuntimeException("Unable to pull container image '" + containerImage + "'", e); + } + } + System.setProperty("test.url", TestHTTPResourceManager.getUri()); if (httpPort == 0) { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java index 2353322d4b664..36dd9ee032936 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java @@ -31,6 +31,7 @@ import javax.enterprise.inject.Alternative; import javax.inject.Inject; +import org.apache.commons.lang3.RandomStringUtils; import org.jboss.jandex.Index; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; @@ -288,6 +289,7 @@ public void accept(String s, String s2) { } }, DevServicesLauncherConfigResultBuildItem.class.getName()); + boolean manageNetwork = false; if (isDockerAppLaunch) { // obtain the ID of the shared network - this needs to be done after the augmentation has been run // or else we run into various ClassLoader problems @@ -297,11 +299,12 @@ public void accept(String s, String s2) { Object sharedNetwork = networkClass.getField("SHARED").get(null); networkId = (String) networkClass.getMethod("getId").invoke(sharedNetwork); } catch (Exception e) { - networkId = null; + networkId = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false); + manageNetwork = true; } } - return new DefaultDevServicesLaunchResult(propertyMap, networkId, curatedApplication); + return new DefaultDevServicesLaunchResult(propertyMap, networkId, manageNetwork, curatedApplication); } static void activateLogging() { @@ -313,12 +316,14 @@ static void activateLogging() { static class DefaultDevServicesLaunchResult implements ArtifactLauncher.InitContext.DevServicesLaunchResult { private final Map properties; private final String networkId; + private final boolean manageNetwork; private final CuratedApplication curatedApplication; DefaultDevServicesLaunchResult(Map properties, String networkId, - CuratedApplication curatedApplication) { + boolean manageNetwork, CuratedApplication curatedApplication) { this.properties = properties; this.networkId = networkId; + this.manageNetwork = manageNetwork; this.curatedApplication = curatedApplication; } @@ -330,6 +335,11 @@ public String networkId() { return networkId; } + @Override + public boolean manageNetwork() { + return manageNetwork; + } + @Override public void close() { curatedApplication.close(); diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java index 6906a0141f63e..d395f4e018fc7 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java @@ -199,6 +199,11 @@ public String networkId() { return null; } + @Override + public boolean manageNetwork() { + return false; + } + @Override public void close() {