diff --git a/bom/pom.xml b/bom/pom.xml index ba88d13718..185f257cbd 100755 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -97,7 +97,7 @@ 1.21 3.1.2 - 3.0.0 + 3.2.6 1.19.0 @@ -996,14 +996,18 @@ ${akka-persistence-inmemory.version} test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - ${flapdoodle.version} + com.github.docker-java + docker-java-core + ${docker-java.version} + test + + + com.github.docker-java + docker-java-transport-zerodep + ${docker-java.version} test - org.openjdk.jmh jmh-core diff --git a/services/concierge/actors/pom.xml b/services/concierge/actors/pom.xml index 574c7ad1ce..b29b1a0ebf 100644 --- a/services/concierge/actors/pom.xml +++ b/services/concierge/actors/pom.xml @@ -66,12 +66,6 @@ awaitility test - - org.eclipse.ditto - ditto-services-utils-test - test-jar - test - ch.qos.logback logback-classic diff --git a/services/connectivity/messaging/pom.xml b/services/connectivity/messaging/pom.xml index bdccfdb9f3..317bf9bb1d 100644 --- a/services/connectivity/messaging/pom.xml +++ b/services/connectivity/messaging/pom.xml @@ -215,11 +215,6 @@ awaitility test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - org.eclipse.ditto ditto-services-utils-test diff --git a/services/connectivity/messaging/src/test/java/org/eclipse/ditto/services/connectivity/messaging/persistence/ConnectionPersistenceActorTest.java b/services/connectivity/messaging/src/test/java/org/eclipse/ditto/services/connectivity/messaging/persistence/ConnectionPersistenceActorTest.java index 625690685d..305650f90f 100644 --- a/services/connectivity/messaging/src/test/java/org/eclipse/ditto/services/connectivity/messaging/persistence/ConnectionPersistenceActorTest.java +++ b/services/connectivity/messaging/src/test/java/org/eclipse/ditto/services/connectivity/messaging/persistence/ConnectionPersistenceActorTest.java @@ -45,7 +45,6 @@ import javax.annotation.Nullable; -import org.apache.commons.compress.utils.Sets; import org.awaitility.Awaitility; import org.eclipse.ditto.json.JsonPointer; import org.eclipse.ditto.json.JsonValue; @@ -142,7 +141,7 @@ */ public final class ConnectionPersistenceActorTest extends WithMockServers { - private static final Set SUBJECTS = Sets.newHashSet(TestConstants.Authorization.SUBJECT_ID, + private static final Set SUBJECTS = Set.of(TestConstants.Authorization.SUBJECT_ID, TestConstants.Authorization.UNAUTHORIZED_SUBJECT_ID); private static final Set TWIN_AND_LIVE_EVENTS = EnumSet.of(StreamingType.EVENTS, StreamingType.LIVE_EVENTS); diff --git a/services/connectivity/starter/pom.xml b/services/connectivity/starter/pom.xml index 2dabe1f36e..688a2f5b30 100644 --- a/services/connectivity/starter/pom.xml +++ b/services/connectivity/starter/pom.xml @@ -104,11 +104,6 @@ test test-jar - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - com.typesafe.akka akka-testkit_${scala.version} diff --git a/services/policies/persistence/pom.xml b/services/policies/persistence/pom.xml index c615537199..0237127e0f 100755 --- a/services/policies/persistence/pom.xml +++ b/services/policies/persistence/pom.xml @@ -98,12 +98,6 @@ com.typesafe.akka akka-stream_${scala.version} - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - ch.qos.logback logback-classic diff --git a/services/policies/starter/pom.xml b/services/policies/starter/pom.xml index 78c38ed55e..83e1320144 100755 --- a/services/policies/starter/pom.xml +++ b/services/policies/starter/pom.xml @@ -111,11 +111,6 @@ akka-testkit_${scala.version} test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - org.eclipse.ditto ditto-services-utils-test diff --git a/services/things/persistence/pom.xml b/services/things/persistence/pom.xml index 441477387b..9b59967fc0 100755 --- a/services/things/persistence/pom.xml +++ b/services/things/persistence/pom.xml @@ -163,12 +163,6 @@ test-jar test - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - ch.qos.logback logback-classic diff --git a/services/things/starter/pom.xml b/services/things/starter/pom.xml index a2ed7421de..e8fb0bb6ac 100755 --- a/services/things/starter/pom.xml +++ b/services/things/starter/pom.xml @@ -111,11 +111,6 @@ akka-testkit_${scala.version} test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - org.eclipse.ditto ditto-services-utils-test diff --git a/services/thingsearch/persistence/pom.xml b/services/thingsearch/persistence/pom.xml index 66df212c1d..1d24c874b0 100755 --- a/services/thingsearch/persistence/pom.xml +++ b/services/thingsearch/persistence/pom.xml @@ -173,8 +173,13 @@ test - de.flapdoodle.embed - de.flapdoodle.embed.mongo + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-zerodep test diff --git a/services/thingsearch/persistence/src/test/java/org/eclipse/ditto/services/thingsearch/persistence/AbstractThingSearchPersistenceITBase.java b/services/thingsearch/persistence/src/test/java/org/eclipse/ditto/services/thingsearch/persistence/AbstractThingSearchPersistenceITBase.java index e64ed56d89..df51289fd0 100644 --- a/services/thingsearch/persistence/src/test/java/org/eclipse/ditto/services/thingsearch/persistence/AbstractThingSearchPersistenceITBase.java +++ b/services/thingsearch/persistence/src/test/java/org/eclipse/ditto/services/thingsearch/persistence/AbstractThingSearchPersistenceITBase.java @@ -40,6 +40,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import com.mongodb.reactivestreams.client.MongoCollection; import com.typesafe.config.Config; @@ -67,7 +68,9 @@ public abstract class AbstractThingSearchPersistenceITBase { protected static QueryBuilderFactory qbf; - private static MongoDbResource mongoResource; + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); + private static DittoMongoClient mongoClient; private MongoCollection thingsCollection; @@ -82,9 +85,6 @@ public static void startMongoResource() { final Config rawTestConfig = ConfigFactory.load("test"); final DefaultLimitsConfig limitsConfig = DefaultLimitsConfig.of(rawTestConfig.getConfig("ditto")); qbf = new MongoQueryBuilderFactory(limitsConfig); - - mongoResource = new MongoDbResource("localhost"); - mongoResource.start(); mongoClient = provideClientWrapper(); } @@ -112,7 +112,7 @@ private TestSearchUpdaterStream provideWritePersistence() { private static DittoMongoClient provideClientWrapper() { return MongoClientWrapper.getBuilder() .connectionString( - "mongodb://" + mongoResource.getBindIp() + ":" + mongoResource.getPort() + "/testSearchDB") + "mongodb://" + MONGO_RESOURCE.getBindIp() + ":" + MONGO_RESOURCE.getPort() + "/testSearchDB") .connectionPoolMaxSize(100) .connectionPoolMaxWaitQueueSize(500000) .connectionPoolMaxWaitTime(Duration.ofSeconds(30)) @@ -159,9 +159,6 @@ public static void stopMongoResource() { if (mongoClient != null) { mongoClient.close(); } - if (mongoResource != null) { - mongoResource.stop(); - } } catch (final IllegalStateException e) { System.err.println("IllegalStateException during shutdown of MongoDB: " + e.getMessage()); } diff --git a/services/thingsearch/starter/pom.xml b/services/thingsearch/starter/pom.xml index 735a48be93..aabc43aa56 100755 --- a/services/thingsearch/starter/pom.xml +++ b/services/thingsearch/starter/pom.xml @@ -122,8 +122,13 @@ test - de.flapdoodle.embed - de.flapdoodle.embed.mongo + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-zerodep test diff --git a/services/thingsearch/starter/src/test/java/org/eclipse/ditto/services/thingsearch/starter/actors/SearchActorIT.java b/services/thingsearch/starter/src/test/java/org/eclipse/ditto/services/thingsearch/starter/actors/SearchActorIT.java index e090679c37..06033c23ff 100644 --- a/services/thingsearch/starter/src/test/java/org/eclipse/ditto/services/thingsearch/starter/actors/SearchActorIT.java +++ b/services/thingsearch/starter/src/test/java/org/eclipse/ditto/services/thingsearch/starter/actors/SearchActorIT.java @@ -51,6 +51,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import com.mongodb.reactivestreams.client.MongoCollection; @@ -72,7 +73,8 @@ public final class SearchActorIT { AuthorizationSubject.newInstance("ditto:ditto")); private static QueryParser queryParser; - private static MongoDbResource mongoResource; + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); private static DittoMongoClient mongoClient; private MongoThingsSearchPersistence readPersistence; @@ -84,8 +86,6 @@ public final class SearchActorIT { @BeforeClass public static void startMongoResource() { queryParser = SearchRootActor.getQueryParser(DefaultLimitsConfig.of(ConfigFactory.empty())); - mongoResource = new MongoDbResource("localhost"); - mongoResource.start(); mongoClient = provideClientWrapper(); } @@ -115,7 +115,7 @@ private static TestSearchUpdaterStream provideWritePersistence() { private static DittoMongoClient provideClientWrapper() { return MongoClientWrapper.getBuilder() .connectionString( - "mongodb://" + mongoResource.getBindIp() + ":" + mongoResource.getPort() + "/testSearchDB") + "mongodb://" + MONGO_RESOURCE.getBindIp() + ":" + MONGO_RESOURCE.getPort() + "/testSearchDB") .build(); } @@ -136,9 +136,6 @@ public static void stopMongoResource() { if (mongoClient != null) { mongoClient.close(); } - if (mongoResource != null) { - mongoResource.stop(); - } } catch (final IllegalStateException e) { System.err.println("IllegalStateException during shutdown of MongoDB: " + e.getMessage()); } diff --git a/services/thingsearch/updater-actors/pom.xml b/services/thingsearch/updater-actors/pom.xml index d0b98cdf92..63ddb171ae 100755 --- a/services/thingsearch/updater-actors/pom.xml +++ b/services/thingsearch/updater-actors/pom.xml @@ -110,14 +110,13 @@ test - org.eclipse.ditto - ditto-services-utils-test - test-jar + com.github.docker-java + docker-java-core test - de.flapdoodle.embed - de.flapdoodle.embed.mongo + com.github.docker-java + docker-java-transport-zerodep test diff --git a/services/utils/persistence/pom.xml b/services/utils/persistence/pom.xml index bc4b95ef78..c75f96e161 100755 --- a/services/utils/persistence/pom.xml +++ b/services/utils/persistence/pom.xml @@ -121,8 +121,13 @@ - de.flapdoodle.embed - de.flapdoodle.embed.mongo + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-zerodep test diff --git a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/indices/IndexInitializerIT.java b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/indices/IndexInitializerIT.java index fc7bcb0f01..8bf29f2379 100644 --- a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/indices/IndexInitializerIT.java +++ b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/indices/IndexInitializerIT.java @@ -25,16 +25,13 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import javax.annotation.Nullable; - import org.eclipse.ditto.services.utils.persistence.mongo.DittoMongoClient; import org.eclipse.ditto.services.utils.persistence.mongo.MongoClientWrapper; import org.eclipse.ditto.services.utils.persistence.mongo.assertions.MongoIndexAssertions; import org.eclipse.ditto.services.utils.test.mongo.MongoDbResource; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import com.mongodb.MongoCommandException; @@ -52,6 +49,9 @@ */ public final class IndexInitializerIT { + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); + private static final int CONNECTION_POOL_MAX_SIZE = 5; private static final int CONNECTION_POOL_MAX_WAIT_QUEUE_SIZE = 5; private static final long CONNECTION_POOL_MAX_WAIT_TIME_SECS = 3L; @@ -89,37 +89,22 @@ public final class IndexInitializerIT { Arrays.asList(DefaultIndexKey.of(FOO_FIELD, IndexDirection.ASCENDING), DefaultIndexKey.of(BAR_FIELD, IndexDirection.ASCENDING)), false); - @Nullable private static MongoDbResource mongoResource; - private ActorSystem system; private Materializer materializer; private DittoMongoClient mongoClient; private IndexInitializer indexInitializerUnderTest; private IndexOperations indexOperations; - @BeforeClass - public static void startMongoResource() { - mongoResource = new MongoDbResource("localhost"); - mongoResource.start(); - } - - @AfterClass - public static void stopMongoResource() { - if (mongoResource != null) { - mongoResource.stop(); - } - } - @Before public void before() { system = ActorSystem.create("AkkaTestSystem"); materializer = SystemMaterializer.get(system).materializer(); - requireNonNull(mongoResource); + requireNonNull(MONGO_RESOURCE); requireNonNull(materializer); mongoClient = MongoClientWrapper.getBuilder() - .hostnameAndPort(mongoResource.getBindIp(), mongoResource.getPort()) + .hostnameAndPort(MONGO_RESOURCE.getBindIp(), MONGO_RESOURCE.getPort()) .defaultDatabaseName(getClass().getSimpleName() + "-" + UUID.randomUUID().toString()) .connectionPoolMaxSize(CONNECTION_POOL_MAX_SIZE) .connectionPoolMaxWaitQueueSize(CONNECTION_POOL_MAX_WAIT_QUEUE_SIZE) diff --git a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/ops/eventsource/MongoEventSourceITAssertions.java b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/ops/eventsource/MongoEventSourceITAssertions.java index 5345983e6e..6e44d0c96f 100644 --- a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/ops/eventsource/MongoEventSourceITAssertions.java +++ b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/ops/eventsource/MongoEventSourceITAssertions.java @@ -36,8 +36,8 @@ import org.eclipse.ditto.signals.commands.namespaces.PurgeNamespace; import org.eclipse.ditto.signals.commands.namespaces.PurgeNamespaceResponse; import org.junit.After; -import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.rules.TestName; import org.mockito.Mockito; @@ -62,10 +62,8 @@ public abstract class MongoEventSourceITAssertions { protected static MongoDbConfig mongoDbConfig; - /** - * Embedded MongoDB resource. - */ - private static MongoDbResource mongoDbResource; + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); protected static String mongoDbUri; protected static PersistenceOperationsConfig persistenceOperationsConfig; @@ -75,10 +73,7 @@ public abstract class MongoEventSourceITAssertions { @BeforeClass public static void startMongoDb() { - mongoDbResource = new MongoDbResource("localhost"); - mongoDbResource.start(); - - mongoDbUri = String.format("mongodb://%s:%s/test", mongoDbResource.getBindIp(), mongoDbResource.getPort()); + mongoDbUri = String.format("mongodb://%s:%s/test", MONGO_RESOURCE.getBindIp(), MONGO_RESOURCE.getPort()); mongoDbConfig = DefaultMongoDbConfig.of(getConfig()); persistenceOperationsConfig = mock(PersistenceOperationsConfig.class); @@ -92,14 +87,6 @@ private static Config getConfig() { return mongoDbTestConfig; } - @AfterClass - public static void tearDown() { - if (null != mongoDbResource) { - mongoDbResource.stop(); - mongoDbResource = null; - } - } - @After public void shutDownActorSystem() { if (actorSystem != null) { diff --git a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoReadJournalIT.java b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoReadJournalIT.java index 418dc2a017..11785ab5b3 100644 --- a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoReadJournalIT.java +++ b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoReadJournalIT.java @@ -26,6 +26,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import com.typesafe.config.Config; @@ -45,10 +46,10 @@ */ public final class MongoReadJournalIT { - private static final String MONGO_HOST = "localhost"; private static final String MONGO_DB = "mongoReadJournalIT"; - private static MongoDbResource mongoResource; + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); private static DittoMongoClient mongoClient; private ActorSystem actorSystem; @@ -57,10 +58,8 @@ public final class MongoReadJournalIT { @BeforeClass public static void startMongoResource() { - mongoResource = new MongoDbResource(MONGO_HOST); - mongoResource.start(); mongoClient = MongoClientWrapper.getBuilder() - .hostnameAndPort(mongoResource.getBindIp(), mongoResource.getPort()) + .hostnameAndPort(MONGO_RESOURCE.getBindIp(), MONGO_RESOURCE.getPort()) .defaultDatabaseName(MONGO_DB) .connectionPoolMaxSize(100) .connectionPoolMaxWaitQueueSize(500_000) @@ -74,9 +73,6 @@ public static void stopMongoResource() { if (null != mongoClient) { mongoClient.close(); } - if (null != mongoResource) { - mongoResource.stop(); - } } catch (final IllegalStateException e) { System.err.println("IllegalStateException during shutdown of MongoDB: " + e.getMessage()); } @@ -85,7 +81,8 @@ public static void stopMongoResource() { @Before public void setUp() { // set persistence plugin Mongo URI for JavaDslReadJournal test - final String mongoUri = String.format("mongodb://%s:%d/%s", MONGO_HOST, mongoResource.getPort(), MONGO_DB); + final String mongoUri = + String.format("mongodb://%s:%d/%s", MONGO_RESOURCE.getBindIp(), MONGO_RESOURCE.getPort(), MONGO_DB); final Config config = ConfigFactory.load("mongo-read-journal-test") .withValue("akka.contrib.persistence.mongodb.mongo.mongouri", ConfigValueFactory.fromAnyRef(mongoUri)); actorSystem = ActorSystem.create("AkkaTestSystem", config); diff --git a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoTimestampPersistenceIT.java b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoTimestampPersistenceIT.java index 30f0e21702..c99d86a8ad 100644 --- a/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoTimestampPersistenceIT.java +++ b/services/utils/persistence/src/test/java/org/eclipse/ditto/services/utils/persistence/mongo/streaming/MongoTimestampPersistenceIT.java @@ -30,6 +30,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import com.mongodb.reactivestreams.client.MongoCollection; @@ -48,20 +49,20 @@ */ public final class MongoTimestampPersistenceIT { - private static MongoDbResource mongoResource; + @ClassRule + public static final MongoDbResource MONGO_RESOURCE = new MongoDbResource(); private static DittoMongoClient mongoClient; private static final String KNOWN_COLLECTION = "knownCollection"; + private ActorSystem actorSystem; private Materializer materializer; private MongoTimestampPersistence syncPersistence; @BeforeClass public static void startMongoResource() { - mongoResource = new MongoDbResource("localhost"); - mongoResource.start(); mongoClient = MongoClientWrapper.getBuilder() - .hostnameAndPort(mongoResource.getBindIp(), mongoResource.getPort()) + .hostnameAndPort(MONGO_RESOURCE.getBindIp(), MONGO_RESOURCE.getPort()) .defaultDatabaseName("mongoTimestampPersistenceIT") .connectionPoolMaxSize(100) .connectionPoolMaxWaitQueueSize(500_000) @@ -75,9 +76,6 @@ public static void stopMongoResource() { if (null != mongoClient) { mongoClient.close(); } - if (null != mongoResource) { - mongoResource.stop(); - } } catch (final IllegalStateException e) { System.err.println("IllegalStateException during shutdown of MongoDB: " + e.getMessage()); } diff --git a/services/utils/test/pom.xml b/services/utils/test/pom.xml index d7ae228982..f8a1b6fb54 100755 --- a/services/utils/test/pom.xml +++ b/services/utils/test/pom.xml @@ -31,10 +31,14 @@ ditto-model-base test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-zerodep test diff --git a/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerContainer.java b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerContainer.java new file mode 100644 index 0000000000..b3a807b157 --- /dev/null +++ b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerContainer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.services.utils.test.mongo; + +import java.util.Arrays; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.ContainerNetwork; +import com.github.dockerjava.api.model.ContainerNetworkSettings; +import com.github.dockerjava.api.model.ContainerPort; + +/** + * Provides an easy way to start, stop and remove the once created docker container. + */ +final class DockerContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(DockerContainer.class); + private static final Integer DOCKER_STOP_TIMEOUT_SECONDS = 5; + private final DockerClient dockerClient; + private final String containerId; + + public DockerContainer(final DockerClient dockerClient, final String containerId) { + this.dockerClient = dockerClient; + this.containerId = containerId; + } + + void start() { + LOGGER.info("Starting docker container with ID <{}>.", containerId); + dockerClient.startContainerCmd(containerId).exec(); + } + + void stop() { + LOGGER.info("Stopping docker container with ID <{}>.", containerId); + dockerClient.stopContainerCmd(containerId).withTimeout(DOCKER_STOP_TIMEOUT_SECONDS).exec(); + } + + void remove() { + LOGGER.info("Removing docker container with ID <{}>.", containerId); + dockerClient.removeContainerCmd(containerId).exec(); + } + + /** + * Translates the given private port (for mongo DB it is for example 27017) to the port it has been bound to + * on the host machine. + * + * @param privatePort the private port (for mongo DB it is for example 27017). + * @return the bound port. + * @throws IllegalArgumentException when the given private port was not exposed by this container. + */ + int getPort(final int privatePort) { + final Container container = getContainer(); + return Arrays.stream(container.getPorts()) + .filter(containerPort -> { + final Integer containerPrivatePort = containerPort.getPrivatePort(); + return containerPrivatePort != null && containerPrivatePort == privatePort && + containerPort.getPublicPort() != null; + }) + .findAny() + .map(ContainerPort::getPublicPort) + .orElseThrow(() -> { + final String message = + String.format("No internal port <%d> exposed in this docker container", privatePort); + return new IllegalArgumentException(message); + }); + } + + String getHostname() { + return Optional.ofNullable(getContainer().getNetworkSettings()) + .map(ContainerNetworkSettings::getNetworks) + .map(networks -> networks.get("bridge")) + .map(ContainerNetwork::getGateway) + .orElseThrow( + () -> new IllegalArgumentException("Could not find a gateway defined for network 'bridge'.") + ); + } + + private Container getContainer() { + return dockerClient.listContainersCmd() + .exec() + .stream() + .filter(container -> container.getId().equals(containerId)) + .findAny() + .orElseThrow(() -> { + final String message = String.format("No container with ID <%s> found.", containerId); + return new IllegalStateException(message); + }); + } + +} diff --git a/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerImagePullHandler.java b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerImagePullHandler.java new file mode 100644 index 0000000000..4191a1a37f --- /dev/null +++ b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/DockerImagePullHandler.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.services.utils.test.mongo; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.model.PullResponseItem; + +/** + * Allows to watch completion of image pulling process and logs its progress. + */ +final class DockerImagePullHandler implements ResultCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(DockerImagePullHandler.class); + + @Nullable + private Closeable closeable; + private final CompletableFuture imagePullFuture; + + private DockerImagePullHandler() { + imagePullFuture = new CompletableFuture<>(); + } + + static DockerImagePullHandler newInstance() { + return new DockerImagePullHandler(); + } + + @Override + public void onStart(final Closeable closeable) { + LOGGER.info("Pulling docker image started. Closable: <{}>.", closeable); + this.closeable = closeable; + } + + @Override + public void onNext(final PullResponseItem pullResponseItem) { + LOGGER.info("Got next pull response item <{}>.", pullResponseItem); + } + + @Override + public void onError(final Throwable throwable) { + LOGGER.error("Got error during pulling image.", throwable); + imagePullFuture.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + LOGGER.info("Pulling docker image completed."); + imagePullFuture.complete(null); + } + + public CompletableFuture getImagePullFuture() { + return imagePullFuture; + } + + @Override + public void close() throws IOException { + if (closeable != null) { + LOGGER.info("Aborting pulling docker image."); + closeable.close(); + imagePullFuture.complete(null); + } + } +} diff --git a/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoContainerFactory.java b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoContainerFactory.java new file mode 100644 index 0000000000..92be102a06 --- /dev/null +++ b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoContainerFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.services.utils.test.mongo; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.ExposedPort; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.PortBinding; +import com.github.dockerjava.api.model.Ports; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.zerodep.ZerodepDockerHttpClient; + +/** + * Responsible for creating and configuring the mongo db docker container that should be started for tests. + */ +final class MongoContainerFactory { + + private static final String MONGO_IMAGE_NAME = "mongo"; + private static final String MONGO_VERSION = "4.2"; + private static final String MONGO_IMAGE_IDENTIFIER = MONGO_IMAGE_NAME + ":" + MONGO_VERSION; + private static final int MONGO_INTERNAL_PORT = 27017; + private static final PortBinding MONGO_PORT_BINDING_TO_RANDOM_PORT = + new PortBinding(Ports.Binding.empty(), ExposedPort.tcp(MONGO_INTERNAL_PORT)); + private static final List MONGO_COMMANDS = Arrays.asList("mongod", "--storageEngine", "wiredTiger"); + + private static final MongoContainerFactory INSTANCE = new MongoContainerFactory(); + + private final DockerClient dockerClient; + + private MongoContainerFactory() { + final DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); + final ZerodepDockerHttpClient httpClient = new ZerodepDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .build(); + dockerClient = DockerClientImpl.getInstance(config, httpClient); + + if (isMongoImageAbsent()) { + pullMongoImage(); + } + } + + /** + * @return returns the singleton instance of this factory. + */ + static MongoContainerFactory getInstance() { + return INSTANCE; + } + + /** + * Creates the mongo docker container with all required configuration and returns it. + * It's not started after it has been returned. + * + * @return the created {@link DockerContainer}. + */ + DockerContainer createMongoContainer() { + return getMongoImageId() + .map(imageId -> dockerClient.createContainerCmd(imageId) + .withCmd(MONGO_COMMANDS) + .withHostConfig(HostConfig.newHostConfig().withPortBindings(MONGO_PORT_BINDING_TO_RANDOM_PORT)) + .exec() + .getId()) + .map(containerId -> new DockerContainer(dockerClient, containerId)) + .orElseThrow( + () -> new IllegalStateException("Could not create container because no image was present.") + ); + } + + private boolean isMongoImageAbsent() { + return getMongoImageId().isEmpty(); + } + + private Optional getMongoImageId() { + return dockerClient.listImagesCmd() + .withImageNameFilter(MONGO_IMAGE_IDENTIFIER) + .exec() + .stream() + .findFirst() + .map(Image::getId); + } + + private void pullMongoImage() { + final DockerImagePullHandler dockerImagePullHandler = DockerImagePullHandler.newInstance(); + dockerClient.pullImageCmd(MONGO_IMAGE_IDENTIFIER).exec(dockerImagePullHandler); + dockerImagePullHandler.getImagePullFuture().join(); + } + +} diff --git a/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoDbResource.java b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoDbResource.java index 5e8b3d1fca..fd2af84f9f 100755 --- a/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoDbResource.java +++ b/services/utils/test/src/test/java/org/eclipse/ditto/services/utils/test/mongo/MongoDbResource.java @@ -12,251 +12,43 @@ */ package org.eclipse.ditto.services.utils.test.mongo; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.SocketException; -import java.net.URI; -import java.util.Optional; -import java.util.function.Supplier; - -import javax.annotation.Nullable; - -import org.junit.Assume; import org.junit.rules.ExternalResource; -import org.slf4j.Logger; - -import de.flapdoodle.embed.mongo.Command; -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.Defaults; -import de.flapdoodle.embed.mongo.config.MongoCmdOptions; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.config.Net; -import de.flapdoodle.embed.mongo.distribution.Version; -import de.flapdoodle.embed.process.config.io.ProcessOutput; -import de.flapdoodle.embed.process.config.store.HttpProxyFactory; -import de.flapdoodle.embed.process.config.store.ImmutableDownloadConfig; -import de.flapdoodle.embed.process.io.progress.StandardConsoleProgressListener; /** * External Mongo DB resource for utilization within tests. */ public final class MongoDbResource extends ExternalResource { - /** - * Environment variable key for a HTTP Proxy. - */ - private static final String HTTP_PROXY_ENV_KEY = "HTTP_PROXY"; - - /** - * Environment variable key for the port number of the MongoDB process. - */ - private static final String MONGO_PORT_ENV_KEY = "MONGO_PORT"; - - private final String bindIp; - private final Integer defaultPort; - private final Logger logger; - - /** - * The MongoDB executable. - */ - private MongodExecutable mongodExecutable; - - /** - * The MongoDB process. - */ - private MongodProcess mongodProcess; - - /** - * Constructs a new {@code MongoDbResource} object. - * - * @param bindIp the IP to bind the DB on - */ - public MongoDbResource(final String bindIp) { - this(bindIp, null, null); - } - - /** - * Constructs a new {@code MongoDbResource} object. - * - * @param bindIp the IP to bind the DB on - * @param logger the logger used for mongod output, may be {@code null} (logging turned off) - */ - public MongoDbResource(final String bindIp, final Logger logger) { - this(bindIp, null, logger); - } - - /** - * Constructs a new {@code MongoDbResource} object. - * - * @param bindIp the IP to bind the DB on - * @param defaultPort the default listening port. Bind a random port if it is {@code null}. - * @param logger the logger used for mongod output, may be {@code null} (logging turned off) - */ - public MongoDbResource(final String bindIp, final Integer defaultPort, final Logger logger) { - this.bindIp = bindIp; - this.defaultPort = defaultPort; - this.logger = logger; - mongodExecutable = null; - mongodProcess = null; - } - - public void start() { - before(); - } + private static final int MONGO_INTERNAL_PORT = 27017; + private final DockerContainer mongoContainer; - public void stop() { - after(); + public MongoDbResource() { + mongoContainer = MongoContainerFactory.getInstance().createMongoContainer(); } @Override protected void before() { - final Optional proxyUppercase = Optional.ofNullable(System.getenv(HTTP_PROXY_ENV_KEY)); - final Optional proxyLowercase = Optional.ofNullable(System.getenv(HTTP_PROXY_ENV_KEY.toLowerCase())); - final Optional httpProxy = proxyUppercase.isPresent() ? proxyUppercase : proxyLowercase; - @Nullable final HttpProxyFactory proxyFactory = httpProxy - .map(URI::create) - .map(proxyURI -> new HttpProxyFactory(proxyURI.getHost(), proxyURI.getPort())) - .orElse(null); - - final int mongoDbPort = defaultPort != null - ? defaultPort - : System.getenv(MONGO_PORT_ENV_KEY) != null - ? Integer.parseInt(System.getenv(MONGO_PORT_ENV_KEY)) - : findFreePort(); - - mongodExecutable = tryToConfigureMongoDb(bindIp, mongoDbPort, proxyFactory, logger); - mongodProcess = tryToStartMongoDb(mongodExecutable); - Assume.assumeTrue("MongoDB resource failed to start.", isHealthy()); + mongoContainer.start(); } @Override protected void after() { - if (mongodProcess != null) { - mongodProcess.stop(); - } - if (mongodExecutable != null) { - mongodExecutable.stop(); - } - } - - /** - * @return whether mongodb started successfully. - */ - public boolean isHealthy() { - return mongodProcess != null && mongodExecutable != null; + mongoContainer.stop(); + mongoContainer.remove(); } /** * @return the port on which the db listens. */ public int getPort() { - return mongodProcess.getConfig().net().getPort(); + return mongoContainer.getPort(MONGO_INTERNAL_PORT); } /** * @return the IP on which the db was bound. */ public String getBindIp() { - return mongodProcess.getConfig().net().getBindIp(); - } - - /** - * This method will return a free port number. - * - * @return the port number. - * @throws IllegalStateException if no free port available. - */ - private static int findFreePort() { - final Supplier freePortFinder = new FreePortFinder(); - return freePortFinder.get(); - } - - private static MongodExecutable tryToConfigureMongoDb(final String bindIp, - final int mongoDbPort, - @Nullable final HttpProxyFactory proxyFactory, - final Logger logger) { - - try { - return configureMongoDb(bindIp, mongoDbPort, proxyFactory, logger); - } catch (final Throwable e) { - return null; - } - } - - private static MongodExecutable configureMongoDb(final String bindIp, - final int mongoDbPort, - @Nullable final HttpProxyFactory proxyFactory, - final Logger logger) throws IOException { - - final Command command = Command.MongoD; - - final ProcessOutput processOutput; - if (logger != null) { - processOutput = ProcessOutput.getInstance("mongod", logger); - } else { - processOutput = ProcessOutput.getDefaultInstanceSilent(); - } - - final ImmutableDownloadConfig.Builder downloadConfigBuilder = - Defaults.downloadConfigFor(command).progressListener(new StandardConsoleProgressListener()); - if (proxyFactory != null) { - downloadConfigBuilder.proxyFactory(proxyFactory); - } - - final MongodStarter mongodStarter = MongodStarter.getInstance(Defaults.runtimeConfigFor(command) - .processOutput(processOutput) - .artifactStore(Defaults.extractedArtifactStoreFor(command) - .withDownloadConfig(downloadConfigBuilder.build())) - .build()); - - return mongodStarter.prepare(MongodConfig.builder() - .net(new Net(bindIp, mongoDbPort, false)) - .version(Version.Main.V3_6) - .cmdOptions(MongoCmdOptions.builder() - .storageEngine("wiredTiger") - .useNoJournal(false) - .build()) - .build()); - } - - private static MongodProcess tryToStartMongoDb(final MongodExecutable mongodExecutable) { - try { - return mongodExecutable.start(); - } catch (final IOException e) { - throw new IllegalStateException("Failed to start MongoDB!", e); - } - } - - private static final class FreePortFinder implements Supplier { - - @Override - public Integer get() { - try (final ServerSocket socket = tryToCreateServerSocket()) { - tryToSetReuseAddress(socket); - return socket.getLocalPort(); - } catch (final IOException e) { - throw new IllegalStateException("Failed to close server socket!", e); - } - } - - private static ServerSocket tryToCreateServerSocket() { - try { - return new ServerSocket(0); - } catch (final IOException e) { - throw new IllegalStateException("Failed to create a ServerSocket object!", e); - } - } - - private static void tryToSetReuseAddress(final ServerSocket serverSocket) { - try { - serverSocket.setReuseAddress(true); - } catch (final SocketException e) { - throw new IllegalStateException("Failed to set reuse address to server socket!", e); - } - } - + return mongoContainer.getHostname(); } }