This is an automated email from the ASF dual-hosted git repository.
baedke pushed a commit to branch issue/oak-11364
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/issue/oak-11364 by this push:
new 57a609fce1 OAK-11364: For oak-jcr tests, support automatic
starting/stopping of docker containers used as RDB servers for RDBDocumentStore
57a609fce1 is described below
commit 57a609fce10f4f20427bd0602e1d99fbca4360dd
Author: Manfred Baedke <[email protected]>
AuthorDate: Wed Jan 8 15:52:09 2025 +0100
OAK-11364: For oak-jcr tests, support automatic starting/stopping of docker
containers used as RDB servers for RDBDocumentStore
done
---
.../oak/jcr/OakDocumentRDBRepositoryStub.java | 12 +-
.../jackrabbit/oak/fixture/DocumentRdbFixture.java | 13 +-
.../jackrabbit/oak/plugins/document/RdbUtils.java | 75 ++++++++++
.../oak/plugins/document/rdb/RdbDockerRule.java | 165 +++++++++++++++++++++
4 files changed, 248 insertions(+), 17 deletions(-)
diff --git
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
index 8f17b3b04e..3d95a311b9 100644
---
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
+++
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
@@ -26,6 +26,7 @@ import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
import
org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -35,16 +36,11 @@ import
org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
*/
public class OakDocumentRDBRepositoryStub extends BaseRepositoryStub {
- protected static final String URL = System.getProperty("rdb.jdbc-url",
"jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
-
- protected static final String USERNAME =
System.getProperty("rdb.jdbc-user", "sa");
-
- protected static final String PASSWD =
System.getProperty("rdb.jdbc-passwd", "");
private final Repository repository;
private static final String fname = (new File("target")).isDirectory() ?
"target/" : "";
- private static final String jdbcUrl = URL.replace("{fname}", fname);
+ private static final String jdbcUrl =
RdbUtils.mapJdbcURL().replace("{fname}", fname);
/**
* Constructor as required by the JCR TCK.
@@ -64,7 +60,7 @@ public class OakDocumentRDBRepositoryStub extends
BaseRepositoryStub {
m = new RDBDocumentNodeStoreBuilder().
memoryCacheSize(64 * 1024 * 1024).
setPersistentCache("target/persistentCache,time").
- setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl,
USERNAME, PASSWD), options).
+ setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl,
RdbUtils.USERNAME, RdbUtils.PASSWD), options).
build();
Jcr jcr = new Jcr(m);
preCreateRepository(jcr);
@@ -83,7 +79,7 @@ public class OakDocumentRDBRepositoryStub extends
BaseRepositoryStub {
public static boolean isAvailable() {
try {
- Connection c =
DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl, USERNAME,
PASSWD);
+ Connection c =
DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl,
RdbUtils.USERNAME, RdbUtils.PASSWD);
c.close();
return true;
}
diff --git
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
index e3f00f4770..a261f53b5d 100644
---
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
+++
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
import
org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -44,18 +45,12 @@ public class DocumentRdbFixture extends NodeStoreFixture {
private final String fname = (new File("target")).isDirectory() ?
"target/" : "";
- private final String pUrl = System.getProperty("rdb.jdbc-url",
"jdbc:h2:file:./{fname}oaktest");
-
- private final String pUser = System.getProperty("rdb.jdbc-user", "sa");
-
- private final String pPasswd = System.getProperty("rdb.jdbc-passwd", "");
-
@Override
public NodeStore createNodeStore() {
String prefix = "T" + Long.toHexString(System.currentTimeMillis());
RDBOptions options = new
RDBOptions().tablePrefix(prefix).dropTablesOnClose(true);
- this.jdbcUrl = pUrl.replace("{fname}", fname);
- DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, pUser,
pPasswd);
+ this.jdbcUrl = RdbUtils.mapJdbcURL().replace("{fname}", fname);
+ DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl,
RdbUtils.USERNAME, RdbUtils.PASSWD);
//do not reuse the whiteboard
setWhiteboard(new DefaultWhiteboard());
RDBDocumentNodeStoreBuilder builder = new
RDBDocumentNodeStoreBuilder();
@@ -83,6 +78,6 @@ public class DocumentRdbFixture extends NodeStoreFixture {
@Override
public String toString() {
- return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl,
this.pUrl);
+ return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl);
}
}
\ No newline at end of file
diff --git
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
new file mode 100755
index 0000000000..3c3fc55340
--- /dev/null
+++
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.jackrabbit.oak.plugins.document;
+
+import org.apache.jackrabbit.oak.plugins.document.rdb.RdbDockerRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RdbUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RdbUtils.class);
+
+ public static final String URL = System.getProperty("rdb.jdbc-url",
"jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
+ public static final String USERNAME = System.getProperty("rdb.jdbc-user",
"sa");
+ public static final String PASSWD = System.getProperty("rdb.jdbc-passwd",
"");
+ public static final String IMAGE = System.getProperty("rdb.docker-image",
"");
+
+ private static AtomicInteger port = new AtomicInteger(-1);
+ private static AtomicReference<String> host = new
AtomicReference<>("localhost");
+
+ static {
+ try {
+ if (RdbDockerRule.isDockerImageAvailable()) {
+ RdbDockerRule rule = new RdbDockerRule();
+ rule.apply(new Statement() {
+ @Override
+ public void evaluate() {
+ port.set(rule.getExposedPort());
+ }
+ }, Description.EMPTY).evaluate();
+ }
+ } catch (Throwable t) {
+ LOG.debug("Failed to initialize docker container", t);
+ }
+ }
+
+ public static String mapJdbcURL() {
+ return mapJdbcURL(URL);
+ }
+
+ public static String mapJdbcURL(String jdbcURL) {
+ if (port.get() > -1) {
+ String normalizedJdbcUri = jdbcURL.replaceFirst("@//",
"//").replaceFirst("@", "//");
+ Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
+ Matcher matcher = pattern.matcher(normalizedJdbcUri);
+ if (matcher.find()) {
+ if (matcher.groupCount() > 1) {
+ return matcher.replaceFirst("//" + host + ":" + port);
+ }
+ }
+ }
+ return jdbcURL;
+ }
+}
diff --git
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
new file mode 100644
index 0000000000..0480e2adc5
--- /dev/null
+++
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.jackrabbit.oak.plugins.document.rdb;
+
+import org.apache.jackrabbit.guava.common.base.Strings;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
+import org.junit.Assume;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.images.RemoteDockerImage;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * A MongoDB {@link GenericContainer}.
+ */
+public class RdbDockerRule extends ExternalResource {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(RdbDockerRule.class);
+
+ private static final AtomicReference<Exception> STARTUP_EXCEPTION = new
AtomicReference<>();
+ private static final boolean RDB_AVAILABLE;
+ private GenericContainer<?> rdbContainer;
+
+ private static DockerImageName IMAGE = null;
+ private static int exposedPort = getPortFromJdbcURL(RdbUtils.URL);
+
+ static {
+ if (!Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
+ IMAGE = DockerImageName.parse(RdbUtils.IMAGE);
+ }
+ boolean dockerAvailable = false;
+ boolean imageAvailable = false;
+ try {
+ dockerAvailable = checkDockerAvailability();
+ if (dockerAvailable) {
+ imageAvailable = checkImageAvailability();
+ } else {
+ LOG.info("docker not available");
+ }
+ } catch (Throwable t) {
+ LOG.error("not able to pull specified docker image: {}, error: ",
RdbUtils.IMAGE, t);
+ }
+ RDB_AVAILABLE = dockerAvailable && imageAvailable;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ if (!RDB_AVAILABLE || rdbContainer != null &&
rdbContainer.isRunning()) {
+ return;
+ }
+ rdbContainer = new GenericContainer<>(IMAGE)
+ .withExposedPorts(exposedPort)
+ .withStartupTimeout(Duration.ofMinutes(5));
+
+ try {
+ long startTime = Instant.now().toEpochMilli();
+ rdbContainer.start();
+ LOG.info("RDB container started in: " +
(Instant.now().toEpochMilli() - startTime) + " ms");
+ } catch (Exception e) {
+ LOG.error("error while starting RDB container, error: ", e);
+ STARTUP_EXCEPTION.set(e);
+ throw e;
+ }
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ before();
+ } catch (Throwable e) {
+ Assume.assumeNoException(STARTUP_EXCEPTION.get());
+ throw e;
+ }
+
+ List<Throwable> errors = new ArrayList<>();
+ try {
+ base.evaluate();
+ } catch (Throwable t) {
+ errors.add(t);
+ }
+ MultipleFailureException.assertEmpty(errors);
+ }
+ };
+ }
+
+ private static boolean checkImageAvailability() throws TimeoutException {
+ if (Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
+ return false;
+ }
+ RemoteDockerImage remoteDockerImage = new RemoteDockerImage(IMAGE);
+ remoteDockerImage.get(1, TimeUnit.MINUTES);
+ return true;
+ }
+
+ private static boolean checkDockerAvailability() {
+ return DockerClientFactory.instance().isDockerAvailable();
+ }
+
+ public static boolean isDockerImageAvailable() {
+ return RDB_AVAILABLE;
+ }
+
+ public int getExposedPort() {
+ return exposedPort;
+ }
+
+ public String getHost() {
+ return rdbContainer.getHost();
+ }
+
+ public int getMappedPort() {
+ return rdbContainer.getMappedPort(exposedPort);
+ }
+
+ public static int getPortFromJdbcURL(String jdbcURL) {
+ String normalizedJdbcUri = jdbcURL.replaceFirst("@//",
"//").replaceFirst("@", "//");
+ Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
+ Matcher matcher = pattern.matcher(normalizedJdbcUri);
+ if (matcher.find()) {
+ if (matcher.groupCount() > 1) {
+ try {
+ return Integer.parseInt(matcher.group(2));
+ } catch (NumberFormatException ignored) {
+ //should not happen
+ }
+ }
+ }
+ return -1;
+ }
+
+}