From 5b9d8bfc17b1502a427aeda025acf3162c9e32b3 Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Tue, 14 Apr 2020 08:47:08 +0200 Subject: [PATCH] Upgrade to Testcontainers 1.14.0 See gh-20936 --- .../spring-boot-autoconfigure/build.gradle | 1 + ...baseAutoConfigurationIntegrationTests.java | 7 +- .../couchbase/CouchbaseContainer.java | 504 ------------------ .../spring-boot-parent/build.gradle | 2 +- src/checkstyle/checkstyle-suppressions.xml | 1 - 5 files changed, 6 insertions(+), 509 deletions(-) delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 07104d004b..eb1d1b84c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -180,6 +180,7 @@ dependencies { testImplementation("org.springframework.kafka:spring-kafka-test") testImplementation("org.springframework.security:spring-security-test") testImplementation("org.testcontainers:cassandra") + testImplementation("org.testcontainers:couchbase") testImplementation("org.testcontainers:elasticsearch") testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:testcontainers") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java index 0b4898e3f3..b58c387734 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java @@ -23,8 +23,9 @@ import com.couchbase.client.core.diagnostics.DiagnosticsResult; import com.couchbase.client.java.Bucket; import com.couchbase.client.java.Cluster; import com.couchbase.client.java.env.ClusterEnvironment; -import com.couchbase.client.java.manager.bucket.BucketSettings; import org.junit.jupiter.api.Test; +import org.testcontainers.couchbase.BucketDefinition; +import org.testcontainers.couchbase.CouchbaseContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -45,9 +46,9 @@ class CouchbaseAutoConfigurationIntegrationTests { private static final String BUCKET_NAME = "cbbucket"; @Container - static final CouchbaseContainer couchbase = new CouchbaseContainer().withClusterAdmin("spring", "password") + static final CouchbaseContainer couchbase = new CouchbaseContainer().withCredentials("spring", "password") .withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10)) - .withNewBucket(BucketSettings.create(BUCKET_NAME)); + .withBucket(new BucketDefinition(BUCKET_NAME)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java deleted file mode 100644 index e3afd141d7..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.couchbase; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.couchbase.client.core.env.SeedNode; -import com.couchbase.client.core.env.TimeoutConfig; -import com.couchbase.client.core.util.UrlQueryStringBuilder; -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.ClusterOptions; -import com.couchbase.client.java.env.ClusterEnvironment; -import com.couchbase.client.java.manager.bucket.BucketSettings; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.command.ExecCreateCmdResponse; -import com.github.dockerjava.api.command.InspectContainerResponse; -import com.github.dockerjava.core.command.ExecStartResultCallback; -import org.apache.commons.compress.utils.Sets; -import org.jetbrains.annotations.NotNull; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.Network; -import org.testcontainers.containers.SocatContainer; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.images.builder.Transferable; -import org.testcontainers.shaded.org.apache.commons.io.IOUtils; -import org.testcontainers.utility.ThrowingFunction; - -import static java.net.HttpURLConnection.HTTP_OK; -import static org.testcontainers.shaded.org.apache.commons.codec.binary.Base64.encodeBase64String; - -/** - * Temporary copy of TestContainers's Couchbase support until it works against Couchbase - * SDK v3. - */ -@SuppressWarnings("resource") -class CouchbaseContainer extends GenericContainer { - - public static final String VERSION = "5.5.1"; - - public static final String DOCKER_IMAGE_NAME = "couchbase/server:"; - - public static final ObjectMapper MAPPER = new ObjectMapper(); - - public static final String STATIC_CONFIG = "/opt/couchbase/etc/couchbase/static_config"; - - public static final String CAPI_CONFIG = "/opt/couchbase/etc/couchdb/default.d/capi.ini"; - - private static final int REQUIRED_DEFAULT_PASSWORD_LENGTH = 6; - - private String memoryQuota = "300"; - - private String indexMemoryQuota = "300"; - - private String clusterUsername = "Administrator"; - - private String clusterPassword = "password"; - - private boolean keyValue = true; - - private boolean query = true; - - private boolean index = true; - - private boolean primaryIndex = false; - - private boolean fts = false; - - private ClusterEnvironment couchbaseEnvironment; - - private Cluster couchbaseCluster; - - private final List newBuckets = new ArrayList<>(); - - private String urlBase; - - private SocatContainer proxy; - - CouchbaseContainer() { - this(DOCKER_IMAGE_NAME + VERSION); - } - - CouchbaseContainer(String imageName) { - super(imageName); - - withNetwork(Network.SHARED); - setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html")); - } - - @Override - public Set getLivenessCheckPortNumbers() { - return Sets.newHashSet(getMappedPort(CouchbasePort.REST)); - } - - @Override - protected void configure() { - if (this.clusterPassword.length() < REQUIRED_DEFAULT_PASSWORD_LENGTH) { - logger().warn("The provided cluster admin password length is less then the default password policy length. " - + "Cluster start will fail if configured password requirements are not met."); - } - } - - @Override - protected void doStart() { - startProxy(getNetworkAliases().get(0)); - try { - super.doStart(); - } - catch (Throwable e) { - this.proxy.stop(); - throw e; - } - } - - private void startProxy(String networkAlias) { - this.proxy = new SocatContainer().withNetwork(getNetwork()); - - for (CouchbasePort port : CouchbaseContainer.CouchbasePort.values()) { - if (port.isDynamic()) { - this.proxy.withTarget(port.getOriginalPort(), networkAlias); - } - else { - this.proxy.addExposedPort(port.getOriginalPort()); - } - } - - this.proxy.setWaitStrategy(null); - this.proxy.start(); - - ExecCreateCmdResponse createCmdResponse = this.dockerClient.execCreateCmd(this.proxy.getContainerId()) - .withCmd("sh", "-c", - Stream.of(CouchbaseContainer.CouchbasePort.values()) - .map(port -> "/usr/bin/socat " + "TCP-LISTEN:" + port.getOriginalPort() - + ",fork,reuseaddr " + "TCP:" + networkAlias + ":" + getMappedPort(port)) - .collect(Collectors.joining(" & ", "true", ""))) - .exec(); - try { - this.dockerClient.execStartCmd(createCmdResponse.getId()).exec(new ExecStartResultCallback()) - .awaitCompletion(10, TimeUnit.SECONDS); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted docker start", e); - } - } - - @Override - public List getExposedPorts() { - return this.proxy.getExposedPorts(); - } - - @Override - public String getContainerIpAddress() { - return this.proxy.getContainerIpAddress(); - } - - @Override - public Integer getMappedPort(int originalPort) { - return this.proxy.getMappedPort(originalPort); - } - - protected Integer getMappedPort(CouchbasePort port) { - return getMappedPort(port.getOriginalPort()); - } - - @Override - public List getBoundPortNumbers() { - return this.proxy.getBoundPortNumbers(); - } - - @Override - public void stop() { - try { - stopCluster(); - this.couchbaseCluster = null; - this.couchbaseEnvironment = null; - } - finally { - Stream.of(super::stop, this.proxy::stop).parallel().forEach(Runnable::run); - } - } - - private void stopCluster() { - getCouchbaseCluster().disconnect(); - getCouchbaseEnvironment().shutdown(); - } - - CouchbaseContainer withNewBucket(BucketSettings bucketSettings) { - this.newBuckets.add(new BucketAndUserSettings(bucketSettings)); - return self(); - } - - private void initUrlBase() { - if (this.urlBase == null) { - this.urlBase = String.format("http://%s:%s", getContainerIpAddress(), getMappedPort(CouchbasePort.REST)); - } - } - - void initCluster() throws Exception { - String poolURL = "/pools/default"; - String poolPayload = "memoryQuota=" + URLEncoder.encode(this.memoryQuota, "UTF-8") + "&indexMemoryQuota=" - + URLEncoder.encode(this.indexMemoryQuota, "UTF-8"); - - String setupServicesURL = "/node/controller/setupServices"; - StringBuilder servicePayloadBuilder = new StringBuilder(); - if (this.keyValue) { - servicePayloadBuilder.append("kv,"); - } - if (this.query) { - servicePayloadBuilder.append("n1ql,"); - } - if (this.index) { - servicePayloadBuilder.append("index,"); - } - if (this.fts) { - servicePayloadBuilder.append("fts,"); - } - String setupServiceContent = "services=" + URLEncoder.encode(servicePayloadBuilder.toString(), "UTF-8"); - - String webSettingsURL = "/settings/web"; - String webSettingsContent = "username=" + URLEncoder.encode(this.clusterUsername, "UTF-8") + "&password=" - + URLEncoder.encode(this.clusterPassword, "UTF-8") + "&port=8091"; - - callCouchbaseRestAPI(poolURL, poolPayload); - callCouchbaseRestAPI(setupServicesURL, setupServiceContent); - callCouchbaseRestAPI(webSettingsURL, webSettingsContent); - - createNodeWaitStrategy().waitUntilReady(this); - callCouchbaseRestAPI("/settings/indexes", - "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized"); - } - - @NotNull - private HttpWaitStrategy createNodeWaitStrategy() { - return new HttpWaitStrategy().forPath("/pools/default/") - .withBasicCredentials(this.clusterUsername, this.clusterPassword).forStatusCode(HTTP_OK) - .forResponsePredicate(response -> { - try { - return Optional.of(MAPPER.readTree(response)).map(n -> n.at("/nodes/0/status")) - .map(JsonNode::asText).map("healthy"::equals).orElse(false); - } - catch (IOException e) { - logger().error("Unable to parse response {}", response); - return false; - } - }); - } - - void createBucket(BucketSettings bucketSetting, boolean primaryIndex) throws IOException { - // Insert Bucket - String payload = convertSettingsToParams(bucketSetting, false).build(); - callCouchbaseRestAPI("/pools/default/buckets/", payload); - - // Check that the bucket is ready before moving on - new HttpWaitStrategy().forPath("/pools/default/buckets/" + bucketSetting.name()) - .withBasicCredentials(this.clusterUsername, this.clusterPassword).forStatusCode(HTTP_OK) - .waitUntilReady(this); - - if (this.index && this.primaryIndex) { - Bucket bucket = getCouchbaseCluster().bucket(bucketSetting.name()); - bucket.waitUntilReady(Duration.ofSeconds(10)); - if (primaryIndex) { - getCouchbaseCluster().queryIndexes().createPrimaryIndex(bucketSetting.name()); - } - } - } - - void callCouchbaseRestAPI(String url, String payload) throws IOException { - initUrlBase(); - - String fullUrl = this.urlBase + url; - HttpURLConnection httpConnection = (HttpURLConnection) ((new URL(fullUrl).openConnection())); - httpConnection.setDoOutput(true); - httpConnection.setRequestMethod("POST"); - httpConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - String encoded = encodeBase64String( - (this.clusterUsername + ":" + this.clusterPassword).getBytes(StandardCharsets.UTF_8)); - - httpConnection.setRequestProperty("Authorization", "Basic " + encoded); - DataOutputStream out = new DataOutputStream(httpConnection.getOutputStream()); - out.writeBytes(payload); - out.flush(); - httpConnection.getResponseCode(); - } - - @Override - protected void containerIsCreated(String containerId) { - patchConfig(STATIC_CONFIG, this::addMappedPorts); - // capi needs a special configuration, see - // https://developer.couchbase.com/documentation/server/current/install/install-ports.html - patchConfig(CAPI_CONFIG, this::replaceCapiPort); - } - - private void patchConfig(String configLocation, ThrowingFunction patchFunction) { - String patchedConfig = copyFileFromContainer(configLocation, - inputStream -> patchFunction.apply(IOUtils.toString(inputStream, StandardCharsets.UTF_8))); - copyFileToContainer(Transferable.of(patchedConfig.getBytes(StandardCharsets.UTF_8)), configLocation); - } - - private String addMappedPorts(String originalConfig) { - String portConfig = Stream.of(CouchbaseContainer.CouchbasePort.values()).filter(port -> !port.isDynamic()) - .map(port -> String.format("{%s, %d}.", port.name, getMappedPort(port))) - .collect(Collectors.joining("\n")); - return String.format("%s\n%s", originalConfig, portConfig); - } - - private String replaceCapiPort(String originalConfig) { - return Arrays.stream(originalConfig.split("\n")) - .map(s -> (s.matches("port\\s*=\\s*" + CouchbaseContainer.CouchbasePort.CAPI.getOriginalPort())) - ? "port = " + getMappedPort(CouchbaseContainer.CouchbasePort.CAPI) : s) - .collect(Collectors.joining("\n")); - } - - @Override - protected void containerIsStarted(InspectContainerResponse containerInfo) { - try { - initCluster(); - } - catch (Exception e) { - throw new RuntimeException("Could not init cluster", e); - } - - if (!this.newBuckets.isEmpty()) { - for (BucketAndUserSettings bucket : this.newBuckets) { - try { - createBucket(bucket.getBucketSettings(), this.primaryIndex); - } - catch (Exception e) { - throw new RuntimeException("Could not create bucket", e); - } - } - } - } - - private Cluster createCouchbaseCluster() { - SeedNode seedNode = SeedNode.create(getContainerIpAddress(), - Optional.of(getMappedPort(CouchbaseContainer.CouchbasePort.MEMCACHED)), - Optional.of(getMappedPort(CouchbaseContainer.CouchbasePort.REST))); - - return Cluster.connect(new HashSet<>(Collections.singletonList(seedNode)), ClusterOptions - .clusterOptions(this.clusterUsername, this.clusterPassword).environment(getCouchbaseEnvironment())); - } - - synchronized ClusterEnvironment getCouchbaseEnvironment() { - if (this.couchbaseEnvironment == null) { - this.couchbaseEnvironment = createCouchbaseEnvironment(); - } - return this.couchbaseEnvironment; - } - - synchronized Cluster getCouchbaseCluster() { - if (this.couchbaseCluster == null) { - this.couchbaseCluster = createCouchbaseCluster(); - } - return this.couchbaseCluster; - } - - private ClusterEnvironment createCouchbaseEnvironment() { - return ClusterEnvironment.builder().timeoutConfig(TimeoutConfig.kvTimeout(Duration.ofSeconds(10))).build(); - } - - CouchbaseContainer withMemoryQuota(String memoryQuota) { - this.memoryQuota = memoryQuota; - return self(); - } - - CouchbaseContainer withIndexMemoryQuota(String indexMemoryQuota) { - this.indexMemoryQuota = indexMemoryQuota; - return self(); - } - - CouchbaseContainer withClusterAdmin(String username, String password) { - this.clusterUsername = username; - this.clusterPassword = password; - return self(); - } - - CouchbaseContainer withKeyValue(boolean keyValue) { - this.keyValue = keyValue; - return self(); - } - - CouchbaseContainer withQuery(boolean query) { - this.query = query; - return self(); - } - - CouchbaseContainer withIndex(boolean index) { - this.index = index; - return self(); - } - - CouchbaseContainer withPrimaryIndex(boolean primaryIndex) { - this.primaryIndex = primaryIndex; - return self(); - } - - public CouchbaseContainer withFts(boolean fts) { - this.fts = fts; - return self(); - } - - enum CouchbasePort { - - REST("rest_port", 8091, true), CAPI("capi_port", 8092, false), QUERY("query_port", 8093, false), FTS( - "fts_http_port", 8094, false), CBAS("cbas_http_port", 8095, false), EVENTING("eventing_http_port", 8096, - false), MEMCACHED_SSL("memcached_ssl_port", 11207, false), MEMCACHED("memcached_port", 11210, - false), REST_SSL("ssl_rest_port", 18091, true), CAPI_SSL("ssl_capi_port", 18092, - false), QUERY_SSL("ssl_query_port", 18093, false), FTS_SSL("fts_ssl_port", - 18094, false), CBAS_SSL("cbas_ssl_port", 18095, - false), EVENTING_SSL("eventing_ssl_port", 18096, false); - - final String name; - - final int originalPort; - - final boolean dynamic; - - CouchbasePort(String name, int originalPort, boolean dynamic) { - this.name = name; - this.originalPort = originalPort; - this.dynamic = dynamic; - } - - public String getName() { - return this.name; - } - - public int getOriginalPort() { - return this.originalPort; - } - - public boolean isDynamic() { - return this.dynamic; - } - - } - - private class BucketAndUserSettings { - - private final BucketSettings bucketSettings; - - BucketAndUserSettings(BucketSettings bucketSettings) { - this.bucketSettings = bucketSettings; - } - - BucketSettings getBucketSettings() { - return this.bucketSettings; - } - - } - - private UrlQueryStringBuilder convertSettingsToParams(final BucketSettings settings, boolean update) { - UrlQueryStringBuilder params = UrlQueryStringBuilder.createForUrlSafeNames(); - - params.add("ramQuotaMB", settings.ramQuotaMB()); - params.add("replicaNumber", settings.numReplicas()); - params.add("flushEnabled", settings.flushEnabled() ? 1 : 0); - params.add("maxTTL", settings.maxTTL()); - params.add("evictionPolicy", settings.ejectionPolicy().alias()); - params.add("compressionMode", settings.compressionMode().alias()); - - // The following values must not be changed on update - if (!update) { - params.add("name", settings.name()); - params.add("bucketType", settings.bucketType().alias()); - params.add("conflictResolutionType", settings.conflictResolutionType().alias()); - params.add("replicaIndex", settings.replicaIndexes() ? 1 : 0); - } - - return params; - } - -} diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index e43098db0e..66225c84e5 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -12,7 +12,7 @@ javaPlatform { dependencies { api(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) - api(enforcedPlatform("org.testcontainers:testcontainers-bom:1.13.0")) + api(enforcedPlatform("org.testcontainers:testcontainers-bom:1.14.0")) constraints { api("com.vaadin.external.google:android-json:0.0.20131108.vaadin1") diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 4ac2e81d18..7eb5d2d0c2 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -46,5 +46,4 @@ -