-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
CassandraContainer.java
162 lines (144 loc) · 6.09 KB
/
CassandraContainer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package org.testcontainers.containers;
import com.datastax.driver.core.Cluster;
import com.github.dockerjava.api.command.InspectContainerResponse;
import org.apache.commons.io.IOUtils;
import org.testcontainers.containers.delegate.CassandraDatabaseDelegate;
import org.testcontainers.delegate.DatabaseDelegate;
import org.testcontainers.ext.ScriptUtils;
import org.testcontainers.ext.ScriptUtils.ScriptLoadException;
import org.testcontainers.utility.MountableFile;
import javax.script.ScriptException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
/**
* Cassandra container
*
* Supports 2.x and 3.x Cassandra versions
*
* @author Eugeny Karpov
*/
public class CassandraContainer<SELF extends CassandraContainer<SELF>> extends GenericContainer<SELF> {
public static final String IMAGE = "cassandra";
public static final Integer CQL_PORT = 9042;
private static final String CONTAINER_CONFIG_LOCATION = "/etc/cassandra";
private static final String USERNAME = "cassandra";
private static final String PASSWORD = "cassandra";
private String configLocation;
private String initScriptPath;
public CassandraContainer() {
this(IMAGE + ":3.11.2");
}
public CassandraContainer(String dockerImageName) {
super(dockerImageName);
addExposedPort(CQL_PORT);
setStartupAttempts(3);
}
@Override
protected void configure() {
optionallyMapResourceParameterAsVolume(CONTAINER_CONFIG_LOCATION, configLocation);
}
@Override
protected void containerIsStarted(InspectContainerResponse containerInfo) {
runInitScriptIfRequired();
}
/**
* Load init script content and apply it to the database if initScriptPath is set
*/
private void runInitScriptIfRequired() {
if (initScriptPath != null) {
try {
URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);
if (resource == null) {
logger().warn("Could not load classpath init script: {}", initScriptPath);
throw new ScriptLoadException("Could not load classpath init script: " + initScriptPath + ". Resource not found.");
}
String cql = IOUtils.toString(resource, StandardCharsets.UTF_8);
DatabaseDelegate databaseDelegate = getDatabaseDelegate();
ScriptUtils.executeDatabaseScript(databaseDelegate, initScriptPath, cql);
} catch (IOException e) {
logger().warn("Could not load classpath init script: {}", initScriptPath);
throw new ScriptLoadException("Could not load classpath init script: " + initScriptPath, e);
} catch (ScriptException e) {
logger().error("Error while executing init script: {}", initScriptPath, e);
throw new ScriptUtils.UncategorizedScriptException("Error while executing init script: " + initScriptPath, e);
}
}
}
/**
* Map (effectively replace) directory in Docker with the content of resourceLocation if resource location is not null
*
* Protected to allow for changing implementation by extending the class
*
* @param pathNameInContainer path in docker
* @param resourceLocation relative classpath to resource
*/
protected void optionallyMapResourceParameterAsVolume(String pathNameInContainer, String resourceLocation) {
Optional.ofNullable(resourceLocation)
.map(MountableFile::forClasspathResource)
.ifPresent(mountableFile -> addFileSystemBind(mountableFile.getResolvedPath(), pathNameInContainer, BindMode.READ_WRITE));
}
/**
* Initialize Cassandra with the custom overridden Cassandra configuration
* <p>
* Be aware, that Docker effectively replaces all /etc/cassandra content with the content of config location, so if
* Cassandra.yaml in configLocation is absent or corrupted, then Cassandra just won't launch
*
* @param configLocation relative classpath with the directory that contains cassandra.yaml and other configuration files
*/
public SELF withConfigurationOverride(String configLocation) {
this.configLocation = configLocation;
return self();
}
/**
* Initialize Cassandra with init CQL script
* <p>
* CQL script will be applied after container is started (see using WaitStrategy)
*
* @param initScriptPath relative classpath resource
*/
public SELF withInitScript(String initScriptPath) {
this.initScriptPath = initScriptPath;
return self();
}
/**
* Get username
*
* By default Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml
* If username and password need to be used, then authenticator should be set as PasswordAuthenticator
* (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials
* user management should be modified
*/
public String getUsername() {
return USERNAME;
}
/**
* Get password
*
* By default Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml
* If username and password need to be used, then authenticator should be set as PasswordAuthenticator
* (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials
* user management should be modified
*/
public String getPassword() {
return PASSWORD;
}
/**
* Get configured Cluster
*
* Can be used to obtain connections to Cassandra in the container
*/
public Cluster getCluster() {
return getCluster(this);
}
public static Cluster getCluster(ContainerState containerState) {
return Cluster.builder()
.addContactPoint(containerState.getContainerIpAddress())
.withPort(containerState.getMappedPort(CQL_PORT))
.build();
}
private DatabaseDelegate getDatabaseDelegate() {
return new CassandraDatabaseDelegate(this);
}
}