Skip to content

Commit

Permalink
Use testcontainers networking capabilities when using @QuarkusIntegra…
Browse files Browse the repository at this point in the history
…tionTest + DevServices

This is done in order to get us off of host networking (which only works on Linux).
The way this works is that when @QuarkusIntegrationTest is meant to launch a container
and DevServices are needed, a build item is pushed into the build that forces
the DevServices to launch in TestContainers' SHARED Network.
The id of network is then used when launching the application as a container.

Fixes: quarkusio#18194
  • Loading branch information
geoand committed Jul 23, 2021
1 parent 590a49e commit 3f36649
Show file tree
Hide file tree
Showing 85 changed files with 2,526 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.deployment.builditem;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildStepBuilder;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled;

/**
* A marker build item that if present during the build, then the containers started by DevServices
* will use a shared network.
* This is mainly useful in integration tests where the application container needs to be able
* to communicate with the services containers
*/
public final class DevServicesSharedNetworkBuildItem extends SimpleBuildItem {

/**
* Generates a {@link List<Consumer<BuildChainBuilder>> build chain builder} which creates a build step
* producing the {@link ProcessInheritIODisabled} build item
*/
public static final class Factory implements Function<Map<String, Object>, List<Consumer<BuildChainBuilder>>> {

@Override
public List<Consumer<BuildChainBuilder>> apply(final Map<String, Object> props) {
return Collections.singletonList((builder) -> {
BuildStepBuilder stepBuilder = builder.addBuildStep((ctx) -> {
ctx.produce(new DevServicesSharedNetworkBuildItem());
});
stepBuilder.produces(DevServicesSharedNetworkBuildItem.class).build();
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;

import org.testcontainers.containers.Db2Container;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.Base58;
import org.testcontainers.utility.DockerImageName;

import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProvider;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProviderBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
import io.quarkus.runtime.LaunchMode;

public class DB2DevServicesProcessor {
Expand All @@ -23,26 +27,18 @@ public class DB2DevServicesProcessor {
public static final String TAG = "11.5.5.1";

@BuildStep
DevServicesDatasourceProviderBuildItem setupDB2() {
DevServicesDatasourceProviderBuildItem setupDB2(
Optional<DevServicesSharedNetworkBuildItem> devServicesSharedNetworkBuildItem) {
return new DevServicesDatasourceProviderBuildItem(DatabaseKind.DB2, new DevServicesDatasourceProvider() {
@Override
public RunningDevServicesDatasource startDatabase(Optional<String> username, Optional<String> password,
Optional<String> datasourceName, Optional<String> imageName, Map<String, String> additionalProperties,
OptionalInt fixedExposedPort, LaunchMode launchMode) {
Db2Container container = new Db2Container(
DockerImageName.parse(imageName.orElse("ibmcom/db2:" + TAG))
.asCompatibleSubstituteFor(DockerImageName.parse("ibmcom/db2"))) {
@Override
protected void configure() {
super.configure();
if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), DB2_PORT);
}
};
}
.withPassword(password.orElse("quarkus"))
.withUsername(username.orElse("quarkus"))
.withDatabaseName(datasourceName.orElse("default"));
Db2Container container = new QuarkusDb2Container(imageName, fixedExposedPort,
devServicesSharedNetworkBuildItem.isPresent())
.withPassword(password.orElse("quarkus"))
.withUsername(username.orElse("quarkus"))
.withDatabaseName(datasourceName.orElse("default"));
additionalProperties.forEach(container::withUrlParam);
container.start();
return new RunningDevServicesDatasource(container.getJdbcUrl(), container.getUsername(),
Expand All @@ -57,4 +53,53 @@ public void close() throws IOException {
});
}

private static class QuarkusDb2Container extends Db2Container {
private final OptionalInt fixedExposedPort;
private final boolean useSharedNetwork;

private String hostName = null;

public QuarkusDb2Container(Optional<String> imageName, OptionalInt fixedExposedPort, boolean useSharedNetwork) {
super(DockerImageName.parse(imageName.orElse("ibmcom/db2:" + DB2DevServicesProcessor.TAG))
.asCompatibleSubstituteFor(DockerImageName.parse("ibmcom/db2")));
this.fixedExposedPort = fixedExposedPort;
this.useSharedNetwork = useSharedNetwork;
}

@Override
protected void configure() {
super.configure();

if (useSharedNetwork) {
// When a shared network is requested for the launched containers, we need to configure
// the container to use it. We also need to create a hostname that will be applied to the returned
// JDBC URL
setNetwork(Network.SHARED);
hostName = "db2-" + Base58.randomString(5);
setNetworkAliases(Collections.singletonList(hostName));

// we need to clear the exposed ports as they don't make sense when the application is going to
// to be communicating with the DB over the same network
setExposedPorts(Collections.emptyList());
return;
}

if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), DB2_PORT);
}
}

@Override
public String getJdbcUrl() {
if (useSharedNetwork) {
// in this case we expose the URL using the network alias we created in 'configure'
// and the container port since the application communicating with this container
// won't be doing port mapping
String additionalUrlParams = constructUrlParameters(":", ";", ";");
return "jdbc:db2://" + hostName + ":" + DB2_PORT + "/" + getDatabaseName() + additionalUrlParams;
} else {
return super.getJdbcUrl();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,41 @@

import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;

import org.testcontainers.containers.MariaDBContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.Base58;
import org.testcontainers.utility.DockerImageName;

import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProvider;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProviderBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
import io.quarkus.runtime.LaunchMode;

public class MariaDBDevServicesProcessor {

public static final String TAG = "10.5.9";
public static final Integer PORT = 3306;

@BuildStep
DevServicesDatasourceProviderBuildItem setupMariaDB() {
DevServicesDatasourceProviderBuildItem setupMariaDB(
Optional<DevServicesSharedNetworkBuildItem> devServicesSharedNetworkBuildItem) {
return new DevServicesDatasourceProviderBuildItem(DatabaseKind.MARIADB, new DevServicesDatasourceProvider() {
@Override
public RunningDevServicesDatasource startDatabase(Optional<String> username, Optional<String> password,
Optional<String> datasourceName, Optional<String> imageName, Map<String, String> additionalProperties,
OptionalInt fixedExposedPort, LaunchMode launchMode) {
MariaDBContainer container = new MariaDBContainer(
DockerImageName.parse(imageName.orElse(MariaDBContainer.IMAGE + ":" + TAG))
.asCompatibleSubstituteFor(DockerImageName.parse(MariaDBContainer.IMAGE))) {
@Override
protected void configure() {
super.configure();
if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), 3306);
}
};
}
.withPassword(password.orElse("quarkus"))
.withUsername(username.orElse("quarkus"))
.withDatabaseName(datasourceName.orElse("default"));
MariaDBContainer container = new QuarkusMariaDBContainer(imageName, fixedExposedPort,
devServicesSharedNetworkBuildItem.isPresent())
.withPassword(password.orElse("quarkus"))
.withUsername(username.orElse("quarkus"))
.withDatabaseName(datasourceName.orElse("default"));
additionalProperties.forEach(container::withUrlParam);
container.start();
return new RunningDevServicesDatasource(container.getJdbcUrl(), container.getUsername(),
Expand All @@ -54,4 +51,50 @@ public void close() throws IOException {
});
}

private static class QuarkusMariaDBContainer extends MariaDBContainer {
private final OptionalInt fixedExposedPort;
private final boolean useSharedNetwork;

private String hostName = null;

public QuarkusMariaDBContainer(Optional<String> imageName, OptionalInt fixedExposedPort, boolean useSharedNetwork) {
super(DockerImageName.parse(imageName.orElse(MariaDBContainer.IMAGE + ":" + MariaDBDevServicesProcessor.TAG))
.asCompatibleSubstituteFor(DockerImageName.parse(MariaDBContainer.IMAGE)));
this.fixedExposedPort = fixedExposedPort;
this.useSharedNetwork = useSharedNetwork;
}

@Override
protected void configure() {
super.configure();

if (useSharedNetwork) {
// When a shared network is requested for the launched containers, we need to configure
// the container to use it. We also need to create a hostname that will be applied to the returned
// JDBC URL
setNetwork(Network.SHARED);
hostName = "mariadb-" + Base58.randomString(5);
setNetworkAliases(Collections.singletonList(hostName));

// we need to clear the exposed ports as they don't make sense when the application is going to
// to be communicating with the DB over the same network
setExposedPorts(Collections.emptyList());
return;
}

if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), PORT);
}
}

@Override
public String getJdbcUrl() {
if (useSharedNetwork) {
String additionalUrlParams = constructUrlParameters("?", "&");
return "jdbc:mariadb://" + hostName + ":" + PORT + "/" + getDatabaseName() + additionalUrlParams;
} else {
return super.getJdbcUrl();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;

import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.Base58;
import org.testcontainers.utility.DockerImageName;

import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProvider;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceProviderBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
import io.quarkus.runtime.LaunchMode;

public class MSSQLDevServicesProcessor {
Expand All @@ -24,25 +28,16 @@ public class MSSQLDevServicesProcessor {
public static final String TAG = "2019-CU10-ubuntu-20.04";

@BuildStep
DevServicesDatasourceProviderBuildItem setupMSSQL() {
DevServicesDatasourceProviderBuildItem setupMSSQL(
Optional<DevServicesSharedNetworkBuildItem> devServicesSharedNetworkBuildItem) {
return new DevServicesDatasourceProviderBuildItem(DatabaseKind.MSSQL, new DevServicesDatasourceProvider() {
@Override
public RunningDevServicesDatasource startDatabase(Optional<String> username, Optional<String> password,
Optional<String> datasourceName, Optional<String> imageName, Map<String, String> additionalProperties,
OptionalInt fixedExposedPort, LaunchMode launchMode) {
JdbcDatabaseContainer container = new MSSQLServerContainer(
DockerImageName
.parse(imageName.orElse(MSSQLServerContainer.IMAGE + ":" + TAG))
.asCompatibleSubstituteFor(MSSQLServerContainer.IMAGE)) {
@Override
protected void configure() {
super.configure();
if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), MSSQLServerContainer.MS_SQL_SERVER_PORT);
}
};
}
.withPassword(password.orElse("Quarkuspassword1"));
JdbcDatabaseContainer container = new QuarkusMSSQLServerContainer(imageName, fixedExposedPort,
devServicesSharedNetworkBuildItem.isPresent())
.withPassword(password.orElse("Quarkuspassword1"));
additionalProperties.forEach(container::withUrlParam);
container.start();
return new RunningDevServicesDatasource(container.getJdbcUrl(), container.getUsername(),
Expand All @@ -57,4 +52,54 @@ public void close() throws IOException {
});
}

private static class QuarkusMSSQLServerContainer extends MSSQLServerContainer {
private final OptionalInt fixedExposedPort;
private final boolean useSharedNetwork;

private String hostName = null;

public QuarkusMSSQLServerContainer(Optional<String> imageName, OptionalInt fixedExposedPort, boolean useSharedNetwork) {
super(DockerImageName
.parse(imageName.orElse(MSSQLServerContainer.IMAGE + ":" + MSSQLDevServicesProcessor.TAG))
.asCompatibleSubstituteFor(MSSQLServerContainer.IMAGE));
this.fixedExposedPort = fixedExposedPort;
this.useSharedNetwork = useSharedNetwork;
}

@Override
protected void configure() {
super.configure();

if (useSharedNetwork) {
// When a shared network is requested for the launched containers, we need to configure
// the container to use it. We also need to create a hostname that will be applied to the returned
// JDBC URL
setNetwork(Network.SHARED);
hostName = "mssql-" + Base58.randomString(5);
setNetworkAliases(Collections.singletonList(hostName));

// we need to clear the exposed ports as they don't make sense when the application is going to
// to be communicating with the DB over the same network
setExposedPorts(Collections.emptyList());
return;
}

if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), MSSQLServerContainer.MS_SQL_SERVER_PORT);
}
}

@Override
public String getJdbcUrl() {
if (useSharedNetwork) {
// in this case we expose the URL using the network alias we created in 'configure'
// and the container port since the application communicating with this container
// won't be doing port mapping
String additionalUrlParams = constructUrlParameters(";", ";");
return "jdbc:sqlserver://" + hostName + ":" + MS_SQL_SERVER_PORT + additionalUrlParams;
} else {
return super.getJdbcUrl();
}
}
}
}
Loading

0 comments on commit 3f36649

Please sign in to comment.