Skip to content

Commit 98656ef

Browse files
committed
feat: Added AbstractRedisEnterpriseContainer to avoid circular reference
1 parent c0d8e99 commit 98656ef

File tree

5 files changed

+165
-108
lines changed

5 files changed

+165
-108
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.redis.testcontainers;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.StandardCharsets;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.testcontainers.DockerClientFactory;
9+
import org.testcontainers.containers.ContainerLaunchException;
10+
import org.testcontainers.containers.GenericContainer;
11+
import org.testcontainers.containers.output.FrameConsumerResultCallback;
12+
import org.testcontainers.containers.output.OutputFrame;
13+
import org.testcontainers.containers.output.ToStringConsumer;
14+
import org.testcontainers.containers.wait.strategy.Wait;
15+
import org.testcontainers.shaded.org.apache.commons.lang3.ClassUtils;
16+
import org.testcontainers.utility.DockerImageName;
17+
import org.testcontainers.utility.TestEnvironment;
18+
19+
import com.github.dockerjava.api.DockerClient;
20+
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
21+
import com.github.dockerjava.api.command.InspectContainerResponse;
22+
import com.github.dockerjava.api.command.InspectExecResponse;
23+
import com.github.dockerjava.api.exception.DockerException;
24+
25+
public abstract class AbstractRedisEnterpriseContainer<T extends AbstractRedisEnterpriseContainer<T>>
26+
extends GenericContainer<T> implements RedisServer {
27+
28+
public static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("redislabs/redis");
29+
public static final String DEFAULT_TAG = "latest";
30+
31+
private static final Logger log = LoggerFactory.getLogger(AbstractRedisEnterpriseContainer.class);
32+
private static final int WEB_UI_PORT = 8443;
33+
private static final String NODE_EXTERNAL_ADDR = "0.0.0.0";
34+
private static final String RLADMIN = "/opt/redislabs/bin/rladmin";
35+
private static final Long EXIT_CODE_SUCCESS = 0L;
36+
37+
protected AbstractRedisEnterpriseContainer(String dockerImageName) {
38+
this(DockerImageName.parse(dockerImageName));
39+
}
40+
41+
protected AbstractRedisEnterpriseContainer(final DockerImageName dockerImageName) {
42+
super(dockerImageName);
43+
addFixedExposedPort(WEB_UI_PORT, WEB_UI_PORT);
44+
withPrivilegedMode(true);
45+
waitingFor(Wait.forLogMessage(".*success: job_scheduler entered RUNNING state, process has stayed up for.*\\n",
46+
1));
47+
}
48+
49+
@Override
50+
protected void containerIsStarted(InspectContainerResponse containerInfo) {
51+
super.containerIsStarted(containerInfo);
52+
try {
53+
createCluster();
54+
} catch (Exception e) {
55+
throw new ContainerLaunchException("Could not initialize Redis Enterprise", e);
56+
}
57+
}
58+
59+
protected void createCluster() throws Exception {
60+
log.info("Creating cluster");
61+
if (!TestEnvironment.dockerExecutionDriverSupportsExec()) {
62+
// at time of writing, this is the expected result in CircleCI.
63+
throw new UnsupportedOperationException(
64+
"Your docker daemon is running the \"lxc\" driver, which doesn't support \"docker exec\".");
65+
}
66+
67+
InspectContainerResponse containerInfo = getContainerInfo();
68+
if (!isRunning(containerInfo)) {
69+
throw new IllegalStateException("execInContainer can only be used while the Container is running");
70+
}
71+
72+
String containerId = containerInfo.getId();
73+
String containerName = containerInfo.getName();
74+
75+
DockerClient dockerClient = DockerClientFactory.instance().client();
76+
77+
String[] commands = new String[] { RLADMIN, "cluster", "create", "name", "cluster.local", "username",
78+
getAdminUserName(), "password", getAdminPassword(), "external_addr", NODE_EXTERNAL_ADDR };
79+
log.debug("{}: Running \"exec\" command: {}", containerName, commands);
80+
final ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
81+
.withAttachStdout(true).withAttachStderr(true).withCmd(commands).withPrivileged(true).exec();
82+
83+
final ToStringConsumer stdoutConsumer = new ToStringConsumer();
84+
final ToStringConsumer stderrConsumer = new ToStringConsumer();
85+
86+
try (FrameConsumerResultCallback callback = new FrameConsumerResultCallback()) {
87+
callback.addConsumer(OutputFrame.OutputType.STDOUT, stdoutConsumer);
88+
callback.addConsumer(OutputFrame.OutputType.STDERR, stderrConsumer);
89+
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();
90+
InspectExecResponse execResponse = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec();
91+
if (EXIT_CODE_SUCCESS.equals(execResponse.getExitCodeLong())) {
92+
return;
93+
}
94+
if (log.isErrorEnabled()) {
95+
log.error("Could not create cluster: {}", stderrConsumer.toString(StandardCharsets.UTF_8));
96+
}
97+
throw new ContainerLaunchException("Could not create cluster");
98+
} catch (InterruptedException e) {
99+
Thread.currentThread().interrupt();
100+
throw new ContainerLaunchException("Could not create cluster", e);
101+
} catch (IOException e) {
102+
log.error("Could not close result callback", e);
103+
}
104+
}
105+
106+
@Override
107+
public String toString() {
108+
return ClassUtils.getShortClassName(getClass());
109+
}
110+
111+
protected abstract String getAdminPassword();
112+
113+
protected abstract String getAdminUserName();
114+
115+
private boolean isRunning(InspectContainerResponse containerInfo) {
116+
try {
117+
return containerInfo != null && Boolean.TRUE.equals(containerInfo.getState().getRunning());
118+
} catch (DockerException e) {
119+
return false;
120+
}
121+
}
122+
123+
@Override
124+
public String getRedisHost() {
125+
return getHost();
126+
}
127+
128+
}

core/testcontainers-redis/src/main/java/com/redis/testcontainers/RedisEnterpriseContainer.java

Lines changed: 21 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,26 @@
11
package com.redis.testcontainers;
22

3-
import java.io.IOException;
4-
import java.nio.charset.StandardCharsets;
53
import java.util.Objects;
64

75
import org.slf4j.Logger;
86
import org.slf4j.LoggerFactory;
9-
import org.testcontainers.DockerClientFactory;
10-
import org.testcontainers.containers.ContainerLaunchException;
11-
import org.testcontainers.containers.GenericContainer;
12-
import org.testcontainers.containers.output.FrameConsumerResultCallback;
13-
import org.testcontainers.containers.output.OutputFrame;
14-
import org.testcontainers.containers.output.ToStringConsumer;
15-
import org.testcontainers.containers.wait.strategy.Wait;
16-
import org.testcontainers.shaded.org.apache.commons.lang3.ClassUtils;
177
import org.testcontainers.utility.DockerImageName;
18-
import org.testcontainers.utility.TestEnvironment;
198

20-
import com.github.dockerjava.api.DockerClient;
21-
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
22-
import com.github.dockerjava.api.command.InspectContainerResponse;
23-
import com.github.dockerjava.api.command.InspectExecResponse;
24-
import com.github.dockerjava.api.exception.DockerException;
259
import com.redis.enterprise.Admin;
2610
import com.redis.enterprise.Database;
2711
import com.redis.enterprise.RedisModule;
2812

29-
public class RedisEnterpriseContainer extends GenericContainer<RedisEnterpriseContainer> implements RedisServer {
13+
public class RedisEnterpriseContainer extends AbstractRedisEnterpriseContainer<RedisEnterpriseContainer> {
3014

31-
public static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("redislabs/redis");
32-
public static final String DEFAULT_TAG = "latest";
3315
public static final int DEFAULT_DATABASE_SHARD_COUNT = 2;
3416
public static final int DEFAULT_DATABASE_PORT = 12000;
3517
public static final String DEFAULT_DATABASE_NAME = "testcontainers";
3618
public static final RedisModule[] DEFAULT_DATABASE_MODULES = { RedisModule.JSON, RedisModule.SEARCH,
3719
RedisModule.TIMESERIES, RedisModule.BLOOM };
3820

3921
private static final Logger log = LoggerFactory.getLogger(RedisEnterpriseContainer.class);
40-
private static final String NODE_EXTERNAL_ADDR = "0.0.0.0";
41-
private static final String RLADMIN = "/opt/redislabs/bin/rladmin";
42-
private static final Long EXIT_CODE_SUCCESS = 0L;
43-
private static final int WEB_UI_PORT = 8443;
4422

23+
private final Admin admin = new Admin();
4524
private Database database = defaultDatabase();
4625

4726
public static Database defaultDatabase() {
@@ -50,17 +29,11 @@ public static Database defaultDatabase() {
5029
}
5130

5231
public RedisEnterpriseContainer(String dockerImageName) {
53-
this(DockerImageName.parse(dockerImageName));
32+
super(dockerImageName);
5433
}
5534

5635
public RedisEnterpriseContainer(final DockerImageName dockerImageName) {
5736
super(dockerImageName);
58-
addFixedExposedPort(Admin.DEFAULT_PORT, Admin.DEFAULT_PORT);
59-
addFixedExposedPort(WEB_UI_PORT, WEB_UI_PORT);
60-
addFixedExposedPort(database.getPort(), database.getPort());
61-
withPrivilegedMode(true);
62-
waitingFor(Wait.forLogMessage(".*success: job_scheduler entered RUNNING state, process has stayed up for.*\\n",
63-
1));
6437
}
6538

6639
public Database getDatabase() {
@@ -75,94 +48,36 @@ public RedisEnterpriseContainer withDatabase(Database database) {
7548
return this;
7649
}
7750

78-
private Admin admin() {
79-
Admin admin = new Admin();
80-
admin.withHost(getRedisHost());
81-
return admin;
82-
}
83-
8451
@Override
85-
protected void containerIsStarted(InspectContainerResponse containerInfo) {
86-
super.containerIsStarted(containerInfo);
87-
try (Admin admin = admin()) {
88-
log.info("Waiting for cluster bootstrap");
89-
admin.waitForBoostrap();
90-
createCluster();
91-
Database response = admin.createDatabase(database);
92-
log.info("Created database {} with UID {}", response.getName(), response.getUid());
93-
} catch (Exception e) {
94-
throw new ContainerLaunchException("Could not initialize Redis Enterprise container", e);
95-
}
96-
}
97-
98-
private void createCluster() {
99-
log.info("Creating cluster");
100-
if (!TestEnvironment.dockerExecutionDriverSupportsExec()) {
101-
// at time of writing, this is the expected result in CircleCI.
102-
throw new UnsupportedOperationException(
103-
"Your docker daemon is running the \"lxc\" driver, which doesn't support \"docker exec\".");
104-
}
105-
106-
InspectContainerResponse containerInfo = getContainerInfo();
107-
if (!isRunning(containerInfo)) {
108-
throw new IllegalStateException("execInContainer can only be used while the Container is running");
109-
}
110-
111-
String containerId = containerInfo.getId();
112-
String containerName = containerInfo.getName();
113-
114-
DockerClient dockerClient = DockerClientFactory.instance().client();
115-
116-
String[] commands = new String[] { RLADMIN, "cluster", "create", "name", "cluster.local", "username",
117-
Admin.DEFAULT_USER_NAME, "password", Admin.DEFAULT_PASSWORD, "external_addr", NODE_EXTERNAL_ADDR };
118-
log.debug("{}: Running \"exec\" command: {}", containerName, commands);
119-
final ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
120-
.withAttachStdout(true).withAttachStderr(true).withCmd(commands).withPrivileged(true).exec();
121-
122-
final ToStringConsumer stdoutConsumer = new ToStringConsumer();
123-
final ToStringConsumer stderrConsumer = new ToStringConsumer();
124-
125-
try (FrameConsumerResultCallback callback = new FrameConsumerResultCallback()) {
126-
callback.addConsumer(OutputFrame.OutputType.STDOUT, stdoutConsumer);
127-
callback.addConsumer(OutputFrame.OutputType.STDERR, stderrConsumer);
128-
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();
129-
InspectExecResponse execResponse = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec();
130-
if (EXIT_CODE_SUCCESS.equals(execResponse.getExitCodeLong())) {
131-
return;
132-
}
133-
if (log.isErrorEnabled()) {
134-
log.error("Could not create cluster: {}", stderrConsumer.toString(StandardCharsets.UTF_8));
135-
}
136-
throw new ContainerLaunchException("Could not create cluster");
137-
} catch (InterruptedException e) {
138-
Thread.currentThread().interrupt();
139-
throw new ContainerLaunchException("Could not create cluster", e);
140-
} catch (IOException e) {
141-
log.error("Could not close result callback", e);
142-
}
52+
protected String getAdminUserName() {
53+
return admin.getUserName();
14354
}
14455

14556
@Override
146-
public boolean isRedisCluster() {
147-
return database.isOssCluster();
57+
protected String getAdminPassword() {
58+
return admin.getPassword();
14859
}
14960

15061
@Override
151-
public String toString() {
152-
return ClassUtils.getShortClassName(RedisEnterpriseContainer.class);
62+
protected void doStart() {
63+
admin.withHost(getHost());
64+
addFixedExposedPort(admin.getPort(), admin.getPort());
65+
addFixedExposedPort(database.getPort(), database.getPort());
66+
super.doStart();
15367
}
15468

155-
private boolean isRunning(InspectContainerResponse containerInfo) {
156-
try {
157-
return containerInfo != null && Boolean.TRUE.equals(containerInfo.getState().getRunning());
158-
} catch (DockerException e) {
159-
return false;
160-
}
69+
@Override
70+
protected void createCluster() throws Exception {
71+
log.info("Waiting for cluster bootstrap");
72+
admin.waitForBoostrap();
73+
super.createCluster();
74+
Database response = admin.createDatabase(database);
75+
log.info("Created database {} with UID {}", response.getName(), response.getUid());
16176
}
16277

16378
@Override
164-
public String getRedisHost() {
165-
return getHost();
79+
public boolean isRedisCluster() {
80+
return database.isOssCluster();
16681
}
16782

16883
@Override

core/testcontainers-redis/testcontainers-redis.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
dependencies {
22
api 'org.testcontainers:testcontainers'
3-
api group: 'com.redis', name: 'redis-enterprise-admin', version: rsAdminVersion
3+
api group: 'com.redis', name: 'redis-enterprise-admin', version: adminVersion
44
testImplementation 'org.slf4j:slf4j-simple'
55
testImplementation 'org.junit.jupiter:junit-jupiter-api'
66
testImplementation 'org.junit.jupiter:junit-jupiter-params'

gradle.properties

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
group = com.redis
2+
sourceCompatibility = 17
3+
targetCompatibility = 17
4+
testSourceCompatibility = 17
5+
testTargetCompatibility = 17
26
reproducibleBuild = true
37

8+
adminVersion = 0.5.3
49
dependencyPluginVersion = 1.1.4
510
kordampBuildVersion = 3.4.0
611
kordampPluginVersion = 0.54.0
712
springBootVersion = 3.2.3
813

9-
rsAdminVersion = 0.5.1
1014
jacocoVersion = 0.8.11
1115
lettucemodVersion = 3.7.3
1216

settings.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,14 @@ projects {
3030
id 'io.spring.dependency-management'
3131
}
3232
}
33+
}
34+
35+
enforce {
36+
rule(enforcer.rules.RequireJavaVersion) { r ->
37+
r.version.set('17')
38+
}
39+
rule(enforcer.rules.BanDuplicateClasses) { r ->
40+
// search only on compile and runtime classpaths
41+
r.configurations.addAll(['compileClasspath', 'runtimeClasspath'])
42+
}
3343
}

0 commit comments

Comments
 (0)