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 <manfred.bae...@gmail.com> 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; + } + +}