Skip to content

Commit 57a609f

Browse files
committed
OAK-11364: For oak-jcr tests, support automatic starting/stopping of docker containers used as RDB servers for RDBDocumentStore
done
1 parent 6049f7a commit 57a609f

File tree

4 files changed

+248
-17
lines changed

4 files changed

+248
-17
lines changed

oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import javax.jcr.RepositoryException;
2727

2828
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
29+
import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
2930
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
3031
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
3132
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -35,16 +36,11 @@
3536
*/
3637
public class OakDocumentRDBRepositoryStub extends BaseRepositoryStub {
3738

38-
protected static final String URL = System.getProperty("rdb.jdbc-url", "jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
39-
40-
protected static final String USERNAME = System.getProperty("rdb.jdbc-user", "sa");
41-
42-
protected static final String PASSWD = System.getProperty("rdb.jdbc-passwd", "");
4339

4440
private final Repository repository;
4541

4642
private static final String fname = (new File("target")).isDirectory() ? "target/" : "";
47-
private static final String jdbcUrl = URL.replace("{fname}", fname);
43+
private static final String jdbcUrl = RdbUtils.mapJdbcURL().replace("{fname}", fname);
4844

4945
/**
5046
* Constructor as required by the JCR TCK.
@@ -64,7 +60,7 @@ public OakDocumentRDBRepositoryStub(Properties settings) throws RepositoryExcept
6460
m = new RDBDocumentNodeStoreBuilder().
6561
memoryCacheSize(64 * 1024 * 1024).
6662
setPersistentCache("target/persistentCache,time").
67-
setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl, USERNAME, PASSWD), options).
63+
setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl, RdbUtils.USERNAME, RdbUtils.PASSWD), options).
6864
build();
6965
Jcr jcr = new Jcr(m);
7066
preCreateRepository(jcr);
@@ -83,7 +79,7 @@ public void run() {
8379

8480
public static boolean isAvailable() {
8581
try {
86-
Connection c = DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl, USERNAME, PASSWD);
82+
Connection c = DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl, RdbUtils.USERNAME, RdbUtils.PASSWD);
8783
c.close();
8884
return true;
8985
}

oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import javax.sql.DataSource;
3030

3131
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
32+
import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
3233
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
3334
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
3435
import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -44,18 +45,12 @@ public class DocumentRdbFixture extends NodeStoreFixture {
4445

4546
private final String fname = (new File("target")).isDirectory() ? "target/" : "";
4647

47-
private final String pUrl = System.getProperty("rdb.jdbc-url", "jdbc:h2:file:./{fname}oaktest");
48-
49-
private final String pUser = System.getProperty("rdb.jdbc-user", "sa");
50-
51-
private final String pPasswd = System.getProperty("rdb.jdbc-passwd", "");
52-
5348
@Override
5449
public NodeStore createNodeStore() {
5550
String prefix = "T" + Long.toHexString(System.currentTimeMillis());
5651
RDBOptions options = new RDBOptions().tablePrefix(prefix).dropTablesOnClose(true);
57-
this.jdbcUrl = pUrl.replace("{fname}", fname);
58-
DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, pUser, pPasswd);
52+
this.jdbcUrl = RdbUtils.mapJdbcURL().replace("{fname}", fname);
53+
DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, RdbUtils.USERNAME, RdbUtils.PASSWD);
5954
//do not reuse the whiteboard
6055
setWhiteboard(new DefaultWhiteboard());
6156
RDBDocumentNodeStoreBuilder builder = new RDBDocumentNodeStoreBuilder();
@@ -83,6 +78,6 @@ public void dispose(NodeStore nodeStore) {
8378

8479
@Override
8580
public String toString() {
86-
return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl, this.pUrl);
81+
return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl);
8782
}
8883
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.jackrabbit.oak.plugins.document;
18+
19+
import org.apache.jackrabbit.oak.plugins.document.rdb.RdbDockerRule;
20+
import org.junit.runner.Description;
21+
import org.junit.runners.model.Statement;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
25+
import java.util.concurrent.atomic.AtomicInteger;
26+
import java.util.concurrent.atomic.AtomicReference;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
29+
30+
public class RdbUtils {
31+
32+
private static final Logger LOG = LoggerFactory.getLogger(RdbUtils.class);
33+
34+
public static final String URL = System.getProperty("rdb.jdbc-url", "jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
35+
public static final String USERNAME = System.getProperty("rdb.jdbc-user", "sa");
36+
public static final String PASSWD = System.getProperty("rdb.jdbc-passwd", "");
37+
public static final String IMAGE = System.getProperty("rdb.docker-image", "");
38+
39+
private static AtomicInteger port = new AtomicInteger(-1);
40+
private static AtomicReference<String> host = new AtomicReference<>("localhost");
41+
42+
static {
43+
try {
44+
if (RdbDockerRule.isDockerImageAvailable()) {
45+
RdbDockerRule rule = new RdbDockerRule();
46+
rule.apply(new Statement() {
47+
@Override
48+
public void evaluate() {
49+
port.set(rule.getExposedPort());
50+
}
51+
}, Description.EMPTY).evaluate();
52+
}
53+
} catch (Throwable t) {
54+
LOG.debug("Failed to initialize docker container", t);
55+
}
56+
}
57+
58+
public static String mapJdbcURL() {
59+
return mapJdbcURL(URL);
60+
}
61+
62+
public static String mapJdbcURL(String jdbcURL) {
63+
if (port.get() > -1) {
64+
String normalizedJdbcUri = jdbcURL.replaceFirst("@//", "//").replaceFirst("@", "//");
65+
Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
66+
Matcher matcher = pattern.matcher(normalizedJdbcUri);
67+
if (matcher.find()) {
68+
if (matcher.groupCount() > 1) {
69+
return matcher.replaceFirst("//" + host + ":" + port);
70+
}
71+
}
72+
}
73+
return jdbcURL;
74+
}
75+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.jackrabbit.oak.plugins.document.rdb;
18+
19+
import org.apache.jackrabbit.guava.common.base.Strings;
20+
import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
21+
import org.junit.Assume;
22+
import org.junit.rules.ExternalResource;
23+
import org.junit.runner.Description;
24+
import org.junit.runners.model.MultipleFailureException;
25+
import org.junit.runners.model.Statement;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
import org.testcontainers.DockerClientFactory;
29+
import org.testcontainers.containers.GenericContainer;
30+
import org.testcontainers.images.RemoteDockerImage;
31+
import org.testcontainers.utility.DockerImageName;
32+
33+
import java.time.Duration;
34+
import java.time.Instant;
35+
import java.util.ArrayList;
36+
import java.util.List;
37+
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.TimeoutException;
39+
import java.util.concurrent.atomic.AtomicReference;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
42+
43+
44+
/**
45+
* A MongoDB {@link GenericContainer}.
46+
*/
47+
public class RdbDockerRule extends ExternalResource {
48+
49+
private static final Logger LOG = LoggerFactory.getLogger(RdbDockerRule.class);
50+
51+
private static final AtomicReference<Exception> STARTUP_EXCEPTION = new AtomicReference<>();
52+
private static final boolean RDB_AVAILABLE;
53+
private GenericContainer<?> rdbContainer;
54+
55+
private static DockerImageName IMAGE = null;
56+
private static int exposedPort = getPortFromJdbcURL(RdbUtils.URL);
57+
58+
static {
59+
if (!Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
60+
IMAGE = DockerImageName.parse(RdbUtils.IMAGE);
61+
}
62+
boolean dockerAvailable = false;
63+
boolean imageAvailable = false;
64+
try {
65+
dockerAvailable = checkDockerAvailability();
66+
if (dockerAvailable) {
67+
imageAvailable = checkImageAvailability();
68+
} else {
69+
LOG.info("docker not available");
70+
}
71+
} catch (Throwable t) {
72+
LOG.error("not able to pull specified docker image: {}, error: ", RdbUtils.IMAGE, t);
73+
}
74+
RDB_AVAILABLE = dockerAvailable && imageAvailable;
75+
}
76+
77+
@Override
78+
protected void before() throws Throwable {
79+
if (!RDB_AVAILABLE || rdbContainer != null && rdbContainer.isRunning()) {
80+
return;
81+
}
82+
rdbContainer = new GenericContainer<>(IMAGE)
83+
.withExposedPorts(exposedPort)
84+
.withStartupTimeout(Duration.ofMinutes(5));
85+
86+
try {
87+
long startTime = Instant.now().toEpochMilli();
88+
rdbContainer.start();
89+
LOG.info("RDB container started in: " + (Instant.now().toEpochMilli() - startTime) + " ms");
90+
} catch (Exception e) {
91+
LOG.error("error while starting RDB container, error: ", e);
92+
STARTUP_EXCEPTION.set(e);
93+
throw e;
94+
}
95+
}
96+
97+
@Override
98+
public Statement apply(Statement base, Description description) {
99+
return new Statement() {
100+
@Override
101+
public void evaluate() throws Throwable {
102+
try {
103+
before();
104+
} catch (Throwable e) {
105+
Assume.assumeNoException(STARTUP_EXCEPTION.get());
106+
throw e;
107+
}
108+
109+
List<Throwable> errors = new ArrayList<>();
110+
try {
111+
base.evaluate();
112+
} catch (Throwable t) {
113+
errors.add(t);
114+
}
115+
MultipleFailureException.assertEmpty(errors);
116+
}
117+
};
118+
}
119+
120+
private static boolean checkImageAvailability() throws TimeoutException {
121+
if (Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
122+
return false;
123+
}
124+
RemoteDockerImage remoteDockerImage = new RemoteDockerImage(IMAGE);
125+
remoteDockerImage.get(1, TimeUnit.MINUTES);
126+
return true;
127+
}
128+
129+
private static boolean checkDockerAvailability() {
130+
return DockerClientFactory.instance().isDockerAvailable();
131+
}
132+
133+
public static boolean isDockerImageAvailable() {
134+
return RDB_AVAILABLE;
135+
}
136+
137+
public int getExposedPort() {
138+
return exposedPort;
139+
}
140+
141+
public String getHost() {
142+
return rdbContainer.getHost();
143+
}
144+
145+
public int getMappedPort() {
146+
return rdbContainer.getMappedPort(exposedPort);
147+
}
148+
149+
public static int getPortFromJdbcURL(String jdbcURL) {
150+
String normalizedJdbcUri = jdbcURL.replaceFirst("@//", "//").replaceFirst("@", "//");
151+
Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
152+
Matcher matcher = pattern.matcher(normalizedJdbcUri);
153+
if (matcher.find()) {
154+
if (matcher.groupCount() > 1) {
155+
try {
156+
return Integer.parseInt(matcher.group(2));
157+
} catch (NumberFormatException ignored) {
158+
//should not happen
159+
}
160+
}
161+
}
162+
return -1;
163+
}
164+
165+
}

0 commit comments

Comments
 (0)