Optionally support multiple cassandra instances in Sidecar
diff --git a/CHANGES.txt b/CHANGES.txt
index 4d0bb18..c72ce7d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
1.0.0
-----
+ * Optionally support multiple cassandra instances in Sidecar (CASSANDRASC-33)
* Call the start method of CassandraAdaptorDelegate to start periodic health check (CASSANDRASC-32)
* Avoid having sidecar's health response code depend on Cassandra's health information (CASSANDRASC-29)
* Add Stream SSTable API to Sidecar to stream SSTable components through zero copy streaming (CASSANDRASC-28)
diff --git a/build.gradle b/build.gradle
index 836c4dd..0cb0c6b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -83,7 +83,7 @@
configFile file("${project.rootDir}/checkstyle.xml")
}
spotbugs {
- toolVersion = '4.0.0'
+ toolVersion = '4.2.3'
excludeFilter = file("${project.rootDir}/spotbugs-exclude.xml")
}
@@ -147,12 +147,12 @@
}
dependencies {
- compile 'io.vertx:vertx-web:3.8.5'
- compile 'io.vertx:vertx-dropwizard-metrics:3.8.5'
- compile 'io.vertx:vertx-web-client:3.8.5'
+ compile "io.vertx:vertx-web:${project.vertxVersion}"
+ compile "io.vertx:vertx-dropwizard-metrics:${project.vertxVersion}"
+ compile "io.vertx:vertx-web-client:${project.vertxVersion}"
compile 'io.swagger.core.v3:swagger-jaxrs2:2.1.0'
- compile 'org.jboss.resteasy:resteasy-vertx:3.1.2.Final'
+ compile "org.jboss.resteasy:resteasy-vertx:4.7.4.Final"
compile group: 'org.jboss.spec.javax.servlet', name: 'jboss-servlet-api_4.0_spec', version: '2.0.0.Final'
// Trying to be exactly compatible with Cassandra's deps
@@ -176,8 +176,8 @@
testCompile group: 'org.cassandraunit', name: 'cassandra-unit-shaded', version: '3.11.2.0'
testCompile 'com.datastax.cassandra:cassandra-driver-core:3.9.0:tests'
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19'
- testCompile group: 'io.vertx', name: 'vertx-junit5', version: '3.8.5'
-
+ testCompile group: 'io.vertx', name: 'vertx-junit5', version: "${project.vertxVersion}"
+
compile project(":common")
compile project(":cassandra40")
}
diff --git a/gradle.properties b/gradle.properties
index 0312136..c9d988b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,3 +2,4 @@
junitVersion=5.4.2
kubernetesClientVersion=9.0.0
cassandra40Version=4.0.1
+vertxVersion=4.2.1
diff --git a/src/main/dist/conf/sidecar.yaml b/src/main/dist/conf/sidecar.yaml
index 39dc0a4..ad1e3a0 100644
--- a/src/main/dist/conf/sidecar.yaml
+++ b/src/main/dist/conf/sidecar.yaml
@@ -1,11 +1,15 @@
#
# Cassandra SideCar configuration file
#
-
-cassandra:
- - host: localhost
- - port: 9042
- - data_dirs: /cassandra/d1/data, /cassandra/d2/data
+cassandra_instances:
+ - id: 1
+ host: localhost1
+ port: 9042
+ data_dirs: /cassandra/d1/data, /cassandra/d2/data
+ - id: 2
+ host: localhost2
+ port: 9042
+ data_dirs: /cassandra/d3/data, /cassandra/d4/data
sidecar:
- host: 0.0.0.0
diff --git a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
index 5644d7b..3355d5d 100644
--- a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
+++ b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
@@ -27,24 +27,22 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.vertx.core.http.HttpServer;
-import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.utils.SslUtils;
/**
* Main class for initiating the Cassandra sidecar
+ * Note: remember to start and stop all delegates of instances
*/
@Singleton
public class CassandraSidecarDaemon
{
private static final Logger logger = LoggerFactory.getLogger(CassandraSidecarDaemon.class);
- private final CassandraAdapterDelegate delegate;
private final HttpServer server;
private final Configuration config;
@Inject
- public CassandraSidecarDaemon(CassandraAdapterDelegate delegate, HttpServer server, Configuration config)
+ public CassandraSidecarDaemon(HttpServer server, Configuration config)
{
- this.delegate = delegate;
this.server = server;
this.config = config;
}
@@ -53,16 +51,16 @@
{
banner(System.out);
validate();
- delegate.start();
logger.info("Starting Cassandra Sidecar on {}:{}", config.getHost(), config.getPort());
server.listen(config.getPort(), config.getHost());
+ this.config.getInstancesConfig().instances().forEach(instanceMetadata -> instanceMetadata.delegate().start());
}
public void stop()
{
logger.info("Stopping Cassandra Sidecar");
- delegate.stop();
server.close();
+ this.config.getInstancesConfig().instances().forEach(instanceMetadata -> instanceMetadata.delegate().stop());
}
private void banner(PrintStream out)
diff --git a/src/main/java/org/apache/cassandra/sidecar/Configuration.java b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
index 088f956..87b97e6 100644
--- a/src/main/java/org/apache/cassandra/sidecar/Configuration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
@@ -18,23 +18,17 @@
package org.apache.cassandra.sidecar;
-import java.util.Collection;
-import java.util.List;
import javax.annotation.Nullable;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+
/**
* Sidecar configuration
*/
public class Configuration
{
- /* Cassandra Host */
- private final String cassandraHost;
-
- /* Cassandra Port */
- private final Integer cassandraPort;
-
- /* Cassandra Data Dirs */
- private Collection<String> cassandraDataDirs;
+ /* Cassandra Instances Config */
+ private final InstancesConfig instancesConfig;
/* Sidecar's HTTP REST API port */
private final Integer port;
@@ -66,16 +60,13 @@
private final long throttleDelayInSeconds;
- public Configuration(String cassandraHost, Integer cassandraPort, List<String> cassandraDataDirs, String host,
- Integer port, Integer healthCheckFrequencyMillis, boolean isSslEnabled,
- @Nullable String keyStorePath, @Nullable String keyStorePassword,
+ public Configuration(InstancesConfig instancesConfig, String host, Integer port, Integer healthCheckFrequencyMillis,
+ boolean isSslEnabled, @Nullable String keyStorePath, @Nullable String keyStorePassword,
@Nullable String trustStorePath, @Nullable String trustStorePassword,
long rateLimitStreamRequestsPerSecond, long throttleTimeoutInSeconds,
long throttleDelayInSeconds)
{
- this.cassandraHost = cassandraHost;
- this.cassandraPort = cassandraPort;
- this.cassandraDataDirs = cassandraDataDirs;
+ this.instancesConfig = instancesConfig;
this.host = host;
this.port = port;
this.healthCheckFrequencyMillis = healthCheckFrequencyMillis;
@@ -91,33 +82,13 @@
}
/**
- * Get the Cassandra host
+ * Get Cassandra Instances config
*
* @return
*/
- public String getCassandraHost()
+ public InstancesConfig getInstancesConfig()
{
- return cassandraHost;
- }
-
- /**
- * Get the Cassandra port
- *
- * @return
- */
- public Integer getCassandraPort()
- {
- return cassandraPort;
- }
-
- /**
- * Get Cassandra data dirs
- *
- * @return
- */
- public Collection<String> getCassandraDataDirs()
- {
- return cassandraDataDirs;
+ return instancesConfig;
}
/**
@@ -229,9 +200,7 @@
*/
public static class Builder
{
- private String cassandraHost;
- private Integer cassandraPort;
- private List<String> cassandraDataDirs;
+ private InstancesConfig instancesConfig;
private String host;
private Integer port;
private Integer healthCheckFrequencyMillis;
@@ -244,21 +213,9 @@
private long throttleTimeoutInSeconds;
private long throttleDelayInSeconds;
- public Builder setCassandraHost(String host)
+ public Builder setInstancesConfig(InstancesConfig instancesConfig)
{
- this.cassandraHost = host;
- return this;
- }
-
- public Builder setCassandraPort(Integer port)
- {
- this.cassandraPort = port;
- return this;
- }
-
- public Builder setCassandraDataDirs(List<String> dataDirs)
- {
- this.cassandraDataDirs = dataDirs;
+ this.instancesConfig = instancesConfig;
return this;
}
@@ -330,10 +287,10 @@
public Configuration build()
{
- return new Configuration(cassandraHost, cassandraPort, cassandraDataDirs, host, port,
- healthCheckFrequencyMillis, isSslEnabled, keyStorePath, keyStorePassword,
- trustStorePath, trustStorePassword, rateLimitStreamRequestsPerSecond,
- throttleTimeoutInSeconds, throttleDelayInSeconds);
+ return new Configuration(instancesConfig, host, port, healthCheckFrequencyMillis, isSslEnabled,
+ keyStorePath, keyStorePassword, trustStorePath, trustStorePassword,
+ rateLimitStreamRequestsPerSecond, throttleTimeoutInSeconds,
+ throttleDelayInSeconds);
}
}
}
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index 701900c..32c3548 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -22,10 +22,17 @@
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.SidecarRateLimiter;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.YAMLConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.tree.ImmutableNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,15 +49,17 @@
import io.vertx.ext.web.handler.LoggerHandler;
import io.vertx.ext.web.handler.StaticHandler;
import org.apache.cassandra.sidecar.cassandra40.Cassandra40Factory;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.cluster.InstancesConfigImpl;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
import org.apache.cassandra.sidecar.common.CQLSession;
-import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
import org.apache.cassandra.sidecar.routes.CassandraHealthService;
import org.apache.cassandra.sidecar.routes.HealthService;
import org.apache.cassandra.sidecar.routes.StreamSSTableComponent;
import org.apache.cassandra.sidecar.routes.SwaggerOpenApiResource;
-import org.apache.cassandra.sidecar.utils.CachedFilePathBuilder;
-import org.apache.cassandra.sidecar.utils.FilePathBuilder;
+import org.apache.cassandra.sidecar.utils.YAMLKeyConstants;
import org.jboss.resteasy.plugins.server.vertx.VertxRegistry;
import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
@@ -137,7 +146,7 @@
@Provides
@Singleton
- public Configuration configuration() throws ConfigurationException, IOException
+ public Configuration configuration(InstancesConfig instancesConfig) throws ConfigurationException, IOException
{
final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
logger.info("Reading configuration from {}", confPath);
@@ -150,20 +159,18 @@
yamlConf.read(stream);
return new Configuration.Builder()
- .setCassandraHost(yamlConf.get(String.class, "cassandra.host"))
- .setCassandraPort(yamlConf.get(Integer.class, "cassandra.port"))
- .setCassandraDataDirs(yamlConf.getList(String.class, "cassandra.data_dirs"))
- .setHost(yamlConf.get(String.class, "sidecar.host"))
- .setPort(yamlConf.get(Integer.class, "sidecar.port"))
- .setHealthCheckFrequency(yamlConf.get(Integer.class, "healthcheck.poll_freq_millis"))
- .setKeyStorePath(yamlConf.get(String.class, "sidecar.ssl.keystore.path", null))
- .setKeyStorePassword(yamlConf.get(String.class, "sidecar.ssl.keystore.password", null))
- .setTrustStorePath(yamlConf.get(String.class, "sidecar.ssl.truststore.path", null))
- .setTrustStorePassword(yamlConf.get(String.class, "sidecar.ssl.truststore.password", null))
- .setSslEnabled(yamlConf.get(Boolean.class, "sidecar.ssl.enabled", false))
- .setRateLimitStreamRequestsPerSecond(yamlConf.getLong("sidecar.throttle.stream_requests_per_sec"))
- .setThrottleTimeoutInSeconds(yamlConf.getLong("sidecar.throttle.timeout_sec"))
- .setThrottleDelayInSeconds(yamlConf.getLong("sidecar.throttle.delay_sec"))
+ .setInstancesConfig(instancesConfig)
+ .setHost(yamlConf.get(String.class, YAMLKeyConstants.HOST))
+ .setPort(yamlConf.get(Integer.class, YAMLKeyConstants.PORT))
+ .setHealthCheckFrequency(yamlConf.get(Integer.class, YAMLKeyConstants.HEALTH_CHECK_FREQ))
+ .setKeyStorePath(yamlConf.get(String.class, YAMLKeyConstants.KEYSTORE_PATH, null))
+ .setKeyStorePassword(yamlConf.get(String.class, YAMLKeyConstants.KEYSTORE_PASSWORD, null))
+ .setTrustStorePath(yamlConf.get(String.class, YAMLKeyConstants.TRUSTSTORE_PATH, null))
+ .setTrustStorePassword(yamlConf.get(String.class, YAMLKeyConstants.TRUSTSTORE_PASSWORD, null))
+ .setSslEnabled(yamlConf.get(Boolean.class, YAMLKeyConstants.SSL_ENABLED, false))
+ .setRateLimitStreamRequestsPerSecond(yamlConf.getLong(YAMLKeyConstants.STREAM_REQUESTS_PER_SEC))
+ .setThrottleTimeoutInSeconds(yamlConf.getLong(YAMLKeyConstants.THROTTLE_TIMEOUT_SEC))
+ .setThrottleDelayInSeconds(yamlConf.getLong(YAMLKeyConstants.THROTTLE_DELAY_SEC))
.build();
}
catch (MalformedURLException e)
@@ -173,13 +180,63 @@
}
@Provides
- public CQLSession session(Configuration config)
+ @Singleton
+ public InstancesConfig getInstancesConfig(CassandraVersionProvider versionProvider)
+ throws ConfigurationException, IOException
{
- String host = config.getCassandraHost();
- Integer port = config.getPort();
- Integer healthCheckFrequencyMillis = config.getHealthCheckFrequencyMillis();
+ final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
+ URL url = new URL(confPath);
- return new CQLSession(host, port, healthCheckFrequencyMillis);
+ try
+ {
+ YAMLConfiguration yamlConf = new YAMLConfiguration();
+ InputStream stream = url.openStream();
+ yamlConf.read(stream);
+ return readInstancesConfig(yamlConf, versionProvider);
+ }
+ catch (MalformedURLException e)
+ {
+ throw new ConfigurationException(String.format("Unable to parse cluster information from '%s'", url));
+ }
+ }
+
+ @VisibleForTesting
+ public InstancesConfigImpl readInstancesConfig(YAMLConfiguration yamlConf, CassandraVersionProvider versionProvider)
+ {
+ final int healthCheckFrequencyMillis = yamlConf.get(Integer.class, YAMLKeyConstants.HEALTH_CHECK_FREQ);
+
+ /* Since we are supporting handling multiple instances in Sidecar optionally, we prefer reading single instance
+ * data over reading multiple instances section
+ */
+ org.apache.commons.configuration2.Configuration singleInstanceConf = yamlConf.subset(
+ YAMLKeyConstants.CASSANDRA_INSTANCE);
+ if (singleInstanceConf != null && !singleInstanceConf.isEmpty())
+ {
+ String host = singleInstanceConf.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_HOST);
+ int port = singleInstanceConf.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_PORT);
+ String dataDirs = singleInstanceConf.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_DATA_DIRS);
+ CQLSession session = new CQLSession(host, port, healthCheckFrequencyMillis);
+ return new InstancesConfigImpl(Collections.singletonList(new InstanceMetadataImpl(1, host, port,
+ Collections.unmodifiableList(Arrays.asList(dataDirs.split(","))), session,
+ versionProvider, healthCheckFrequencyMillis)));
+ }
+
+ List<HierarchicalConfiguration<ImmutableNode>> instances = yamlConf.configurationsAt(
+ YAMLKeyConstants.CASSANDRA_INSTANCES);
+ final List<InstanceMetadata> instanceMetas = new LinkedList<>();
+ for (HierarchicalConfiguration<ImmutableNode> instance : instances)
+ {
+ int id = instance.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_ID);
+ String host = instance.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_HOST);
+ int port = instance.get(Integer.class, YAMLKeyConstants.CASSANDRA_INSTANCE_PORT);
+ String dataDirs = instance.get(String.class, YAMLKeyConstants.CASSANDRA_INSTANCE_DATA_DIRS);
+
+ CQLSession session = new CQLSession(host, port, healthCheckFrequencyMillis);
+ instanceMetas.add(new InstanceMetadataImpl(id, host, port,
+ Collections.unmodifiableList(Arrays.asList(dataDirs.split(","))), session, versionProvider,
+ healthCheckFrequencyMillis));
+ }
+ return new InstancesConfigImpl(instanceMetas);
}
@Provides
@@ -192,22 +249,9 @@
}
@Provides
- public CassandraAdapterDelegate cassandraAdapterDelegate(CassandraVersionProvider provider, CQLSession session,
- Configuration config)
- {
- return new CassandraAdapterDelegate(provider, session, config.getHealthCheckFrequencyMillis());
- }
-
- @Provides
- public SidecarRateLimiter rateLimiter(Configuration config)
+ @Singleton
+ public SidecarRateLimiter streamRequestRateLimiter(Configuration config)
{
return SidecarRateLimiter.create(config.getRateLimitStreamRequestsPerSecond());
}
-
- @Provides
- @Singleton
- public FilePathBuilder filePathBuilder(Configuration config)
- {
- return new CachedFilePathBuilder(config.getCassandraDataDirs());
- }
}
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java
new file mode 100644
index 0000000..a4a295c
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java
@@ -0,0 +1,30 @@
+package org.apache.cassandra.sidecar.cluster;
+
+import java.util.List;
+
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+
+/**
+ * Maintains metadata of instances maintained by Sidecar.
+ */
+public interface InstancesConfig
+{
+ /**
+ * returns metadata of instances owned by the sidecar
+ */
+ List<InstanceMetadata> instances();
+
+ /**
+ * Lookup instance metadata by id.
+ * @param id instance's id
+ * @return instance meta information
+ */
+ InstanceMetadata instanceFromId(final int id);
+
+ /**
+ * Lookup instance metadata by host name.
+ * @param host host address of instance
+ * @return instance meta information
+ */
+ InstanceMetadata instanceFromHost(final String host);
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
new file mode 100644
index 0000000..189e820
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
@@ -0,0 +1,51 @@
+package org.apache.cassandra.sidecar.cluster;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+
+/**
+ * Local implementation of InstancesConfig.
+ */
+public class InstancesConfigImpl implements InstancesConfig
+{
+ private final Map<Integer, InstanceMetadata> idToInstanceMetas;
+ private final Map<String, InstanceMetadata> hostToInstanceMetas;
+ private final List<InstanceMetadata> instanceMetas;
+
+ public InstancesConfigImpl(List<InstanceMetadata> instanceMetas)
+ {
+ this.idToInstanceMetas = instanceMetas.stream()
+ .collect(Collectors.toMap(InstanceMetadata::id,
+ instanceMeta -> instanceMeta));
+ this.hostToInstanceMetas = instanceMetas.stream()
+ .collect(Collectors.toMap(InstanceMetadata::host,
+ instanceMeta -> instanceMeta));
+ this.instanceMetas = instanceMetas;
+ }
+
+ public List<InstanceMetadata> instances()
+ {
+ return instanceMetas;
+ }
+
+ public InstanceMetadata instanceFromId(int id)
+ {
+ if (!idToInstanceMetas.containsKey(id))
+ {
+ throw new IllegalArgumentException("Instance id " + id + " not found");
+ }
+ return idToInstanceMetas.get(id);
+ }
+
+ public InstanceMetadata instanceFromHost(String host)
+ {
+ if (!hostToInstanceMetas.containsKey(host))
+ {
+ throw new IllegalArgumentException("Instance with host address " + host + " not found");
+ }
+ return hostToInstanceMetas.get(host);
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
new file mode 100644
index 0000000..9081cb9
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
@@ -0,0 +1,48 @@
+package org.apache.cassandra.sidecar.cluster.instance;
+
+import java.util.List;
+
+import org.apache.cassandra.sidecar.common.CQLSession;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.utils.FilePathBuilder;
+
+/**
+ * Metadata of an instance
+ */
+public interface InstanceMetadata
+{
+ /**
+ * Instance id.
+ */
+ int id();
+
+ /**
+ * Host address of cassandra instance.
+ */
+ String host();
+
+ /**
+ * Port number of cassandra instance.
+ */
+ int port();
+
+ /**
+ * List of data directories of cassandra instance.
+ */
+ List<String> dataDirs();
+
+ /**
+ * CQLSession for connecting with instance.
+ */
+ CQLSession session();
+
+ /**
+ * Delegate specific for the instance.
+ */
+ CassandraAdapterDelegate delegate();
+
+ /**
+ * Maintain one path builder for one instance.
+ */
+ FilePathBuilder pathBuilder();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
new file mode 100644
index 0000000..987fdc2
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
@@ -0,0 +1,71 @@
+package org.apache.cassandra.sidecar.cluster.instance;
+
+import java.util.List;
+
+import org.apache.cassandra.sidecar.common.CQLSession;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
+import org.apache.cassandra.sidecar.utils.CachedFilePathBuilder;
+import org.apache.cassandra.sidecar.utils.FilePathBuilder;
+
+/**
+ * Local implementation of InstanceMetadata.
+ */
+public class InstanceMetadataImpl implements InstanceMetadata
+{
+ private final int id;
+ private final String host;
+ private final int port;
+ private final List<String> dataDirs;
+ private final CQLSession session;
+ private final CassandraAdapterDelegate delegate;
+ private final FilePathBuilder pathBuilder;
+
+ public InstanceMetadataImpl(int id, String host, int port, List<String> dataDirs, CQLSession session,
+ CassandraVersionProvider versionProvider, int healthCheckFrequencyMillis)
+ {
+ this.id = id;
+ this.host = host;
+ this.port = port;
+ this.dataDirs = dataDirs;
+ this.pathBuilder = new CachedFilePathBuilder(dataDirs);
+
+ this.session = new CQLSession(host, port, healthCheckFrequencyMillis);
+ this.delegate = new CassandraAdapterDelegate(versionProvider, session, healthCheckFrequencyMillis);
+ }
+
+ public int id()
+ {
+ return id;
+ }
+
+ public String host()
+ {
+ return host;
+ }
+
+ public int port()
+ {
+ return port;
+ }
+
+ public List<String> dataDirs()
+ {
+ return dataDirs;
+ }
+
+ public CQLSession session()
+ {
+ return session;
+ }
+
+ public CassandraAdapterDelegate delegate()
+ {
+ return delegate;
+ }
+
+ public FilePathBuilder pathBuilder()
+ {
+ return pathBuilder;
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/models/ComponentInfo.java b/src/main/java/org/apache/cassandra/sidecar/models/ComponentInfo.java
new file mode 100644
index 0000000..0588ce7
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/models/ComponentInfo.java
@@ -0,0 +1,40 @@
+package org.apache.cassandra.sidecar.models;
+
+/**
+ * Stores information needed to identify a SStable component.
+ */
+public class ComponentInfo
+{
+ private final String keyspace;
+ private final String table;
+ private final String snapshot;
+ private final String component;
+
+ public ComponentInfo(String keyspace, String table, String snapshot, String component)
+ {
+ this.keyspace = keyspace;
+ this.table = table;
+ this.snapshot = snapshot;
+ this.component = component;
+ }
+
+ public String getKeyspace()
+ {
+ return keyspace;
+ }
+
+ public String getTable()
+ {
+ return table;
+ }
+
+ public String getSnapshot()
+ {
+ return snapshot;
+ }
+
+ public String getComponent()
+ {
+ return component;
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthService.java b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthService.java
index cc17ef8..5c71224 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthService.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthService.java
@@ -20,7 +20,9 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -31,25 +33,27 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;
-import io.vertx.core.logging.Logger;
-import io.vertx.core.logging.LoggerFactory;
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
+
+import static org.apache.cassandra.sidecar.utils.RequestUtils.extractHostAddressWithoutPort;
/**
* Provides a simple REST endpoint to determine if a node is available
*/
@Singleton
-@Path("/api/v1/cassandra/__health")
+@Path("/api")
+@Produces(MediaType.APPLICATION_JSON)
public class CassandraHealthService
{
- private static final Logger logger = LoggerFactory.getLogger(HealthService.class);
- private final CassandraAdapterDelegate cassandra;
+ private final InstanceMetadataFetcher metadataFetcher;
@Inject
- public CassandraHealthService(CassandraAdapterDelegate cassandra)
+ public CassandraHealthService(InstanceMetadataFetcher metadataFetcher)
{
- this.cassandra = cassandra;
+ this.metadataFetcher = metadataFetcher;
}
@Operation(summary = "Health Check for Cassandra's status",
@@ -58,11 +62,32 @@
@ApiResponse(responseCode = "200", description = "Cassandra is available"),
@ApiResponse(responseCode = "503", description = "Cassandra is not available")
})
- @Produces(MediaType.APPLICATION_JSON)
@GET
- public Response getCassandraHealth()
+ @Path("/v1/cassandra/__health")
+ public Response getCassandraHealth(@Context HttpServerRequest req)
{
- Boolean up = cassandra.isUp();
+ final String host = req.host();
+ final CassandraAdapterDelegate cassandra = metadataFetcher.getDelegate(extractHostAddressWithoutPort(host));
+ return getHealthResponse(cassandra);
+ }
+
+ @Operation(summary = "Health Check for a particular cassandra instance's status",
+ description = "Returns HTTP 200 if Cassandra instance is available, 503 otherwise",
+ responses = {
+ @ApiResponse(responseCode = "200", description = "Cassandra is available"),
+ @ApiResponse(responseCode = "503", description = "Cassandra is not available")
+ })
+ @GET
+ @Path("/v1/cassandra/instance/{instanceId}/__health")
+ public Response getCassandraHealthForInstance(@PathParam("instanceId") int instanceId)
+ {
+ final CassandraAdapterDelegate cassandra = metadataFetcher.getDelegate(instanceId);
+ return getHealthResponse(cassandra);
+ }
+
+ private Response getHealthResponse(CassandraAdapterDelegate cassandra)
+ {
+ final boolean up = cassandra.isUp();
int status = up ? HttpResponseStatus.OK.code() : HttpResponseStatus.SERVICE_UNAVAILABLE.code();
return Response.status(status).entity(Json.encode(ImmutableMap.of("status", up ?
"OK" : "NOT_OK"))).build();
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java b/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java
index dd0a46a..e2795c6 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java
@@ -30,8 +30,6 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.vertx.core.json.Json;
-import io.vertx.core.logging.Logger;
-import io.vertx.core.logging.LoggerFactory;
/**
* Provides a simple REST endpoint to determine if Sidecar is available
@@ -40,8 +38,6 @@
@Path("/api/v1/__health")
public class HealthService
{
- private static final Logger logger = LoggerFactory.getLogger(HealthService.class);
-
@Operation(summary = "Health Check for Sidecar's status",
description = "Returns HTTP 200 if Sidecar is available")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponent.java b/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponent.java
index 7eb3da8..dc4596e 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponent.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponent.java
@@ -14,17 +14,22 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
+import org.apache.cassandra.sidecar.models.ComponentInfo;
import org.apache.cassandra.sidecar.models.HttpResponse;
import org.apache.cassandra.sidecar.models.Range;
import org.apache.cassandra.sidecar.utils.FilePathBuilder;
import org.apache.cassandra.sidecar.utils.FileStreamer;
+import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
+
+import static org.apache.cassandra.sidecar.utils.RequestUtils.extractHostAddressWithoutPort;
/**
* Handler for serving SSTable components from snapshot folders
*/
@Singleton
-@javax.ws.rs.Path("/api/v1/stream/keyspace/{keyspace}/table/{table}/snapshot/{snapshot}/component/{component}")
+@javax.ws.rs.Path("/api")
public class StreamSSTableComponent
{
private static final Pattern REGEX_DIR = Pattern.compile("[a-zA-Z0-9_-]+");
@@ -32,37 +37,62 @@
private static final Set<String> FORBIDDEN_DIRS = new HashSet<>(
Arrays.asList("system_schema", "system_traces", "system_distributed", "system", "system_auth"));
- private final FilePathBuilder pathBuilder;
+ private final InstanceMetadataFetcher metadataFetcher;
private final FileStreamer fileStreamer;
@Inject
- public StreamSSTableComponent(final FilePathBuilder pathBuilder, final FileStreamer fileStreamer)
+ public StreamSSTableComponent(final InstanceMetadataFetcher metadataFetcher, final FileStreamer fileStreamer)
{
- this.pathBuilder = pathBuilder;
+ this.metadataFetcher = metadataFetcher;
this.fileStreamer = fileStreamer;
}
@GET
- public void handle(@PathParam("keyspace") String keyspace, @PathParam("table") String table,
- @PathParam("snapshot") String snapshot, @PathParam("component") String component,
- @HeaderParam("Range") String range, @Context HttpServerResponse resp)
+ @javax.ws.rs.Path("/v1/stream/keyspace/{keyspace}/table/{table}/snapshot/{snapshot}/component/{component}")
+ public void streamFromFirstInstance(@PathParam("keyspace") String keyspace, @PathParam("table") String table,
+ @PathParam("snapshot") String snapshot,
+ @PathParam("component") String component, @HeaderParam("Range") String range,
+ @Context HttpServerResponse resp, @Context HttpServerRequest req)
+ {
+ final String host = extractHostAddressWithoutPort(req.host());
+ stream(new ComponentInfo(keyspace, table, snapshot, component), range, null, host, resp);
+ }
+
+ @GET
+ @javax.ws.rs.Path
+ ("/v1/stream/instance/{instanceId}/keyspace/{keyspace}/table/{table}/snapshot/{snapshot}/component/{component}")
+ public void streamFromSpecificInstance(@PathParam("keyspace") String keyspace, @PathParam("table") String table,
+ @PathParam("snapshot") String snapshot,
+ @PathParam("component") String component,
+ @PathParam("instanceId") Integer instanceId,
+ @HeaderParam("Range") String range, @Context HttpServerResponse resp)
+ {
+ stream(new ComponentInfo(keyspace, table, snapshot, component), range, instanceId, null, resp);
+ }
+
+ private void stream(ComponentInfo componentInfo, String range, Integer instanceId,
+ String host, HttpServerResponse resp)
{
final HttpResponse response = new HttpResponse(resp);
- if (FORBIDDEN_DIRS.contains(keyspace))
+ if (FORBIDDEN_DIRS.contains(componentInfo.getKeyspace()))
{
- response.setForbiddenStatus(keyspace + " keyspace is forbidden");
+ response.setForbiddenStatus(componentInfo.getKeyspace() + " keyspace is forbidden");
return;
}
- if (!arePathParamsValid(keyspace, table, snapshot, component))
+ if (!arePathParamsValid(componentInfo))
{
response.setBadRequestStatus("Invalid path params found");
return;
}
final Path path;
+ final FilePathBuilder pathBuilder = instanceId == null
+ ? metadataFetcher.getPathBuilder(host)
+ : metadataFetcher.getPathBuilder(instanceId);
try
{
- path = pathBuilder.build(keyspace, table, snapshot, component);
+ path = pathBuilder.build(componentInfo.getKeyspace(), componentInfo.getTable(),
+ componentInfo.getSnapshot(), componentInfo.getComponent());
}
catch (FileNotFoundException e)
{
@@ -83,10 +113,12 @@
fileStreamer.stream(response, file, r);
}
- private boolean arePathParamsValid(String keyspace, String table, String snapshot, String component)
+ private boolean arePathParamsValid(ComponentInfo componentInfo)
{
- return REGEX_DIR.matcher(keyspace).matches() && REGEX_DIR.matcher(table).matches()
- && REGEX_DIR.matcher(snapshot).matches() && REGEX_COMPONENT.matcher(component).matches();
+ return REGEX_DIR.matcher(componentInfo.getKeyspace()).matches()
+ && REGEX_DIR.matcher(componentInfo.getTable()).matches()
+ && REGEX_DIR.matcher(componentInfo.getSnapshot()).matches()
+ && REGEX_COMPONENT.matcher(componentInfo.getComponent()).matches();
}
private Range parseRangeHeader(String rangeHeader, long fileSize)
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/CachedFilePathBuilder.java b/src/main/java/org/apache/cassandra/sidecar/utils/CachedFilePathBuilder.java
index 0c3aabd..6468f9b 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/CachedFilePathBuilder.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/CachedFilePathBuilder.java
@@ -18,12 +18,10 @@
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
/**
* Path builder that caches intermediate paths
*/
-@Singleton
public class CachedFilePathBuilder extends FilePathBuilder
{
private static final Logger logger = LoggerFactory.getLogger(CachedFilePathBuilder.class);
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java b/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
new file mode 100644
index 0000000..0ae7fe1
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
@@ -0,0 +1,60 @@
+package org.apache.cassandra.sidecar.utils;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
+
+/**
+ * To fetch instance information according to instance id provided.
+ * By default returns 1st instance's information
+ */
+@Singleton
+public class InstanceMetadataFetcher
+{
+ private final InstancesConfig instancesConfig;
+
+ @Inject
+ public InstanceMetadataFetcher(InstancesConfig instancesConfig)
+ {
+ this.instancesConfig = instancesConfig;
+ }
+
+ public CassandraAdapterDelegate getDelegate(String host)
+ {
+ return host == null
+ ? getFirstInstance().delegate()
+ : instancesConfig.instanceFromHost(host).delegate();
+ }
+
+ public CassandraAdapterDelegate getDelegate(Integer instanceId)
+ {
+ return instanceId == null
+ ? getFirstInstance().delegate()
+ : instancesConfig.instanceFromId(instanceId).delegate();
+ }
+
+ public FilePathBuilder getPathBuilder(String host)
+ {
+ return host == null
+ ? getFirstInstance().pathBuilder()
+ : instancesConfig.instanceFromHost(host).pathBuilder();
+ }
+
+ public FilePathBuilder getPathBuilder(Integer instanceId)
+ {
+ return instanceId == null
+ ? getFirstInstance().pathBuilder()
+ : instancesConfig.instanceFromId(instanceId).pathBuilder();
+ }
+
+ private InstanceMetadata getFirstInstance()
+ {
+ if (instancesConfig.instances().isEmpty())
+ {
+ throw new IllegalStateException("There are no instances configured!");
+ }
+ return instancesConfig.instances().get(0);
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/RequestUtils.java b/src/main/java/org/apache/cassandra/sidecar/utils/RequestUtils.java
new file mode 100644
index 0000000..22ffdcc
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/RequestUtils.java
@@ -0,0 +1,29 @@
+package org.apache.cassandra.sidecar.utils;
+
+/**
+ * Utility class for Http request related operations.
+ */
+public class RequestUtils
+{
+ /**
+ * Given a combined host address like 127.0.0.1:9042 or [2001:db8:0:0:0:ff00:42:8329]:9042, this method
+ * removes port information and returns 127.0.0.1 or 2001:db8:0:0:0:ff00:42:8329.
+ * @param address
+ * @return host address without port information
+ */
+ public static String extractHostAddressWithoutPort(String address)
+ {
+ if (address.contains(":"))
+ {
+ // just ipv6 host name present without port information
+ if (address.split(":").length > 2 && !address.startsWith("["))
+ {
+ return address;
+ }
+ String host = address.substring(0, address.lastIndexOf(':'));
+ // remove brackets from ipv6 addresses
+ return host.startsWith("[") ? host.substring(1, host.length() - 1) : host;
+ }
+ return address;
+ }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java b/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java
new file mode 100644
index 0000000..0b73866
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/YAMLKeyConstants.java
@@ -0,0 +1,29 @@
+package org.apache.cassandra.sidecar.utils;
+
+/**
+ * Stores keys used to retrieve information from sidecar.yaml file.
+ */
+public class YAMLKeyConstants
+{
+ public static final String HOST = "sidecar.host";
+ public static final String PORT = "sidecar.port";
+ public static final String HEALTH_CHECK_FREQ = "healthcheck.poll_freq_millis";
+ public static final String KEYSTORE_PATH = "sidecar.ssl.keystore.path";
+ public static final String KEYSTORE_PASSWORD = "sidecar.ssl.keystore.password";
+ public static final String TRUSTSTORE_PATH = "sidecar.ssl.truststore.path";
+ public static final String TRUSTSTORE_PASSWORD = "sidecar.ssl.truststore.password";
+ public static final String SSL_ENABLED = "sidecar.ssl.enabled";
+ public static final String STREAM_REQUESTS_PER_SEC = "sidecar.throttle.stream_requests_per_sec";
+ public static final String THROTTLE_TIMEOUT_SEC = "sidecar.throttle.timeout_sec";
+ public static final String THROTTLE_DELAY_SEC = "sidecar.throttle.delay_sec";
+
+ // v1 cassandra instance key constants
+ public static final String CASSANDRA_INSTANCE = "cassandra";
+
+ // v2 cassandra instances key constants
+ public static final String CASSANDRA_INSTANCES = "cassandra_instances";
+ public static final String CASSANDRA_INSTANCE_ID = "id";
+ public static final String CASSANDRA_INSTANCE_HOST = "host";
+ public static final String CASSANDRA_INSTANCE_PORT = "port";
+ public static final String CASSANDRA_INSTANCE_DATA_DIRS = "data_dirs";
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
index 9b19b8f..a6801a8 100644
--- a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
@@ -41,9 +41,7 @@
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.codec.BodyCodec;
import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.routes.HealthService;
-import static org.mockito.Mockito.when;
/**
* Provides basic tests shared between SSL and normal http health services
@@ -55,7 +53,6 @@
private Vertx vertx;
private Configuration config;
private HttpServer server;
- private CassandraAdapterDelegate cassandra;
public abstract boolean isSslEnabled();
@@ -72,8 +69,6 @@
{
Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(getTestModule()));
server = injector.getInstance(HttpServer.class);
- cassandra = injector.getInstance(CassandraAdapterDelegate.class);
-
service = injector.getInstance(HealthService.class);
vertx = injector.getInstance(Vertx.class);
config = injector.getInstance(Configuration.class);
@@ -129,11 +124,10 @@
return options;
}
- @DisplayName("Should return HTTP 200 OK when check=True")
+ @DisplayName("Should return HTTP 200 OK when cassandra instance is up")
@Test
public void testHealthCheckReturns200OK(VertxTestContext testContext)
{
- when(cassandra.isUp()).thenReturn(true);
WebClient client = getClient();
client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health")
@@ -147,14 +141,13 @@
})));
}
- @DisplayName("Should return HTTP 503 Failure when check=False")
+ @DisplayName("Should return HTTP 503 Failure when instance is down")
@Test
public void testHealthCheckReturns503Failure(VertxTestContext testContext)
{
- when(cassandra.isUp()).thenReturn(false);
WebClient client = getClient();
- client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health")
+ client.get(config.getPort(), "localhost", "/api/v1/cassandra/instance/2/__health")
.as(BodyCodec.string())
.ssl(isSslEnabled())
.send(testContext.succeeding(response -> testContext.verify(() ->
diff --git a/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java
new file mode 100644
index 0000000..86d3632
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/ConfigurationTest.java
@@ -0,0 +1,74 @@
+package org.apache.cassandra.sidecar;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.commons.configuration2.YAMLConfiguration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.util.Modules;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test changes related to sidecar.yaml file.
+ */
+public class ConfigurationTest
+{
+ private CassandraVersionProvider versionProvider;
+
+ @BeforeEach
+ void setUp()
+ {
+ Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
+ versionProvider = injector.getInstance(CassandraVersionProvider.class);
+ }
+
+ @Test
+ public void testOldSidecarYAMLFormatWithSingleInstance() throws Exception
+ {
+ MainModule mainModule = new MainModule();
+ YAMLConfiguration yamlConf = new YAMLConfiguration();
+ try (InputStream stream = new FileInputStream("src/test/resources/sidecar_single_instance.yaml"))
+ {
+ yamlConf.read(stream);
+ InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
+ assertEquals(1, instancesConfig.instances().size());
+ assertEquals("localhost", instancesConfig.instances().get(0).host());
+ assertEquals(9042, instancesConfig.instances().get(0).port());
+ }
+ }
+
+ @Test
+ public void testReadingSingleInstanceSectionOverMultipleInstances() throws Exception
+ {
+ MainModule mainModule = new MainModule();
+ YAMLConfiguration yamlConf = new YAMLConfiguration();
+ try (InputStream stream = new FileInputStream("src/test/resources/sidecar_with_single_multiple_instances.yaml"))
+ {
+ yamlConf.read(stream);
+ InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
+ assertEquals(1, instancesConfig.instances().size());
+ assertEquals("localhost", instancesConfig.instances().get(0).host());
+ assertEquals(9042, instancesConfig.instances().get(0).port());
+ }
+ }
+
+ @Test
+ public void testReadingMultipleInstances() throws Exception
+ {
+ MainModule mainModule = new MainModule();
+ YAMLConfiguration yamlConf = new YAMLConfiguration();
+ try (InputStream stream = new FileInputStream("src/test/resources/sidecar_multiple_instances.yaml"))
+ {
+ yamlConf.read(stream);
+ InstancesConfig instancesConfig = mainModule.readInstancesConfig(yamlConf, versionProvider);
+ assertEquals(2, instancesConfig.instances().size());
+ }
+ }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/FilePathBuilderTest.java b/src/test/java/org/apache/cassandra/sidecar/FilePathBuilderTest.java
index 2f37749..10ffafd 100644
--- a/src/test/java/org/apache/cassandra/sidecar/FilePathBuilderTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/FilePathBuilderTest.java
@@ -12,6 +12,7 @@
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Modules;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
import org.apache.cassandra.sidecar.utils.CachedFilePathBuilder;
import org.apache.cassandra.sidecar.utils.FilePathBuilder;
import org.assertj.core.api.Assertions;
@@ -24,7 +25,7 @@
*/
public class FilePathBuilderTest
{
- private static final String expectedFilePath = "src/test/resources/data/TestKeyspace" +
+ private static final String expectedFilePath = "src/test/resources/instance1/data/TestKeyspace" +
"/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot" +
"/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
private static FilePathBuilder pathBuilder;
@@ -33,7 +34,7 @@
public static void setUp()
{
Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
- pathBuilder = injector.getInstance(FilePathBuilder.class);
+ pathBuilder = injector.getInstance(InstancesConfig.class).instances().get(0).pathBuilder();
}
@Test
@@ -73,7 +74,7 @@
{
pathBuilder.build(keyspace, table, snapshot, component);
});
- String msg = "Table random not found, path searched: src/test/resources/data/TestKeyspace";
+ String msg = "Table random not found, path searched: src/test/resources/instance1/data/TestKeyspace";
assertEquals(msg, thrownException.getMessage());
}
@@ -88,7 +89,7 @@
{
pathBuilder.build(keyspace, table, snapshot, component);
});
- String msg = "Snapshot random not found, path searched: src/test/resources/data/TestKeyspace" +
+ String msg = "Snapshot random not found, path searched: src/test/resources/instance1/data/TestKeyspace" +
"/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b";
assertEquals(msg, thrownException.getMessage());
}
diff --git a/src/test/java/org/apache/cassandra/sidecar/StreamSSTableComponentTest.java b/src/test/java/org/apache/cassandra/sidecar/StreamSSTableComponentTest.java
index b6cc605..ffbf3bb 100644
--- a/src/test/java/org/apache/cassandra/sidecar/StreamSSTableComponentTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/StreamSSTableComponentTest.java
@@ -81,7 +81,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/random/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(404, response.statusCode());
@@ -95,7 +95,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/random/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(404, response.statusCode());
@@ -109,7 +109,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/system/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(403, response.statusCode());
@@ -124,7 +124,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/k*s/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(400, response.statusCode());
@@ -139,7 +139,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data...db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(400, response.statusCode());
@@ -154,7 +154,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Digest.crc32d";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.send(context.succeeding(response -> context.verify(() ->
{
assertEquals(400, response.statusCode());
@@ -169,7 +169,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable/snapshot/TestSnapshot/component" +
"/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=0-")
.as(BodyCodec.buffer())
.send(context.succeeding(response -> context.verify(() ->
@@ -186,7 +186,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=4-3")
.send(context.succeeding(response -> context.verify(() ->
{
@@ -201,7 +201,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=5-9")
.send(context.succeeding(response -> context.verify(() ->
{
@@ -216,7 +216,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=5-")
.send(context.succeeding(response -> context.verify(() ->
{
@@ -231,7 +231,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=0-999999")
.as(BodyCodec.buffer())
.send(context.succeeding(response -> context.verify(() ->
@@ -248,7 +248,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=0-2") // 3 bytes streamed
.as(BodyCodec.buffer())
.send(context.succeeding(response -> context.verify(() ->
@@ -265,7 +265,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=-2") // last 2 bytes streamed
.as(BodyCodec.buffer())
.send(context.succeeding(response -> context.verify(() ->
@@ -282,7 +282,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bytes=-5")
.send(context.succeeding(response -> context.verify(() ->
{
@@ -297,7 +297,7 @@
WebClient client = WebClient.create(vertx);
String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshot" +
"/TestSnapshot/component/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
- client.get(config.getPort(), config.getHost(), "/api/v1/stream" + testRoute)
+ client.get(config.getPort(), "localhost", "/api/v1/stream" + testRoute)
.putHeader("Range", "bits=0-2")
.send(context.succeeding(response -> context.verify(() ->
{
@@ -305,4 +305,21 @@
context.completeNow();
})));
}
+
+ @Test
+ void testStreamingFromSpecificInstance(VertxTestContext context)
+ {
+ WebClient client = WebClient.create(vertx);
+ String testRoute = "/keyspace/TestKeyspace/table/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/" +
+ "snapshot/TestSnapshot/component/" +
+ "TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
+ client.get(config.getPort(), "localhost", "/api/v1/stream/instance/2" + testRoute)
+ .as(BodyCodec.buffer())
+ .send(context.succeeding(response -> context.verify(() ->
+ {
+ assertEquals(200, response.statusCode());
+ assertEquals("data", response.bodyAsString());
+ context.completeNow();
+ })));
+ }
}
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestModule.java b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 4cf7d9f..892dcd7 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -18,7 +18,9 @@
package org.apache.cassandra.sidecar;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,11 +28,17 @@
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.cluster.InstancesConfigImpl;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
import org.apache.cassandra.sidecar.common.MockCassandraFactory;
+import org.apache.cassandra.sidecar.utils.CachedFilePathBuilder;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* Provides the basic dependencies for unit tests.
@@ -56,9 +64,7 @@
protected Configuration abstractConfig()
{
return new Configuration.Builder()
- .setCassandraHost("INVALID_FOR_TEST")
- .setCassandraPort(0)
- .setCassandraDataDirs(Collections.singletonList("src/test/resources/data"))
+ .setInstancesConfig(getInstancesConfig())
.setHost("127.0.0.1")
.setPort(6475)
.setHealthCheckFrequency(1000)
@@ -69,6 +75,41 @@
.build();
}
+ @Provides
+ @Singleton
+ public InstancesConfig getInstancesConfig()
+ {
+ return new InstancesConfigImpl(getInstanceMetas());
+ }
+
+ public List<InstanceMetadata> getInstanceMetas()
+ {
+ InstanceMetadata instance1 = getMockInstance("localhost", 1, "src/test/resources/instance1/data", true);
+ InstanceMetadata instance2 = getMockInstance("localhost2", 2, "src/test/resources/instance2/data", false);
+ InstanceMetadata instance3 = getMockInstance("localhost3", 3, "src/test/resources/instance3/data", true);
+ final List<InstanceMetadata> instanceMetas = new ArrayList<>();
+ instanceMetas.add(instance1);
+ instanceMetas.add(instance2);
+ instanceMetas.add(instance3);
+ return instanceMetas;
+ }
+
+ private InstanceMetadata getMockInstance(String host, int id, String dataDir, boolean isUp)
+ {
+ InstanceMetadata instanceMeta = mock(InstanceMetadata.class);
+ when(instanceMeta.id()).thenReturn(id);
+ when(instanceMeta.host()).thenReturn(host);
+ when(instanceMeta.port()).thenReturn(6475);
+ when(instanceMeta.pathBuilder()).thenReturn(new CachedFilePathBuilder(Collections.singletonList(dataDir)));
+ when(instanceMeta.dataDirs()).thenReturn(Collections.singletonList(dataDir));
+
+ CassandraAdapterDelegate delegate = mock(CassandraAdapterDelegate.class);
+ when(delegate.isUp()).thenReturn(isUp);
+ doNothing().when(delegate).start();
+ when(instanceMeta.delegate()).thenReturn(delegate);
+ return instanceMeta;
+ }
+
/**
* The Mock factory is used for testing purposes, enabling us to test all failures and possible results
* @return
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
index 4ecb9f2..a76ce13 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
@@ -19,7 +19,6 @@
package org.apache.cassandra.sidecar;
import java.io.File;
-import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,9 +50,7 @@
}
return new Configuration.Builder()
- .setCassandraHost("INVALID_FOR_TEST")
- .setCassandraPort(0)
- .setCassandraDataDirs(Collections.singletonList("src/test/resources/data"))
+ .setInstancesConfig(getInstancesConfig())
.setHost("127.0.0.1")
.setPort(6475)
.setHealthCheckFrequency(1000)
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/RequestUtilsTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/RequestUtilsTest.java
new file mode 100644
index 0000000..8788fb3
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/RequestUtilsTest.java
@@ -0,0 +1,53 @@
+package org.apache.cassandra.sidecar.utils;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * RequestUtilsTest
+ */
+public class RequestUtilsTest
+{
+ @Test
+ public void testAddressWithIPv4Host()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("127.0.0.1");
+ assertEquals("127.0.0.1", host);
+ }
+
+ @Test
+ public void testAddressIPv4HostAndPort()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("127.0.0.1:9043");
+ assertEquals("127.0.0.1", host);
+ }
+
+ @Test
+ public void testAddressWithIPv6Host()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("2001:db8:0:0:0:ff00:42:8329");
+ assertEquals("2001:db8:0:0:0:ff00:42:8329", host);
+ }
+
+ @Test
+ public void testAddressWithIPv6HostAndPort()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("[2001:db8:0:0:0:ff00:42:8329]:9043");
+ assertEquals("2001:db8:0:0:0:ff00:42:8329", host);
+ }
+
+ @Test
+ public void testAddressWithIPv6HostShortcut()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("::1");
+ assertEquals("::1", host);
+ }
+
+ @Test
+ public void testAddressWithIPv6HostShortcutWithPort()
+ {
+ final String host = RequestUtils.extractHostAddressWithoutPort("[::1]:9043");
+ assertEquals("::1", host);
+ }
+}
diff --git a/src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db b/src/test/resources/instance1/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
similarity index 100%
rename from src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
rename to src/test/resources/instance1/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
diff --git a/src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db b/src/test/resources/instance2/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
similarity index 100%
copy from src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
copy to src/test/resources/instance2/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
diff --git a/src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db b/src/test/resources/instance3/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
similarity index 100%
copy from src/test/resources/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
copy to src/test/resources/instance3/data/TestKeyspace/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots/TestSnapshot/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db
diff --git a/src/test/resources/sidecar_multiple_instances.yaml b/src/test/resources/sidecar_multiple_instances.yaml
new file mode 100644
index 0000000..5b8d280
--- /dev/null
+++ b/src/test/resources/sidecar_multiple_instances.yaml
@@ -0,0 +1,35 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra_instances:
+ - id: 1
+ host: localhost1
+ port: 9042
+ data_dirs: /cassandra/d1/data, /cassandra/d2/data
+ - id: 2
+ host: localhost2
+ port: 9042
+ data_dirs: /cassandra/d3/data, /cassandra/d4/data
+
+sidecar:
+ - host: 0.0.0.0
+ - port: 9043
+ - throttle:
+ - stream_requests_per_sec: 5000
+ - delay_sec: 5
+ - timeout_sec: 10
+#
+# Enable SSL configuration (Disabled by default)
+#
+# - ssl:
+# - enabled: true
+# - keystore:
+# - path: "path/to/keystore.p12"
+# - password: password
+# - truststore:
+# - path: "path/to/truststore.p12"
+# - password: password
+
+
+healthcheck:
+ - poll_freq_millis: 30000
\ No newline at end of file
diff --git a/src/test/resources/sidecar_single_instance.yaml b/src/test/resources/sidecar_single_instance.yaml
new file mode 100644
index 0000000..a3823a6
--- /dev/null
+++ b/src/test/resources/sidecar_single_instance.yaml
@@ -0,0 +1,30 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra:
+ - host: localhost
+ - port: 9042
+ - data_dirs: /cassandra/d1/data, /cassandra/d2/data
+
+sidecar:
+ - host: 0.0.0.0
+ - port: 9043
+ - throttle:
+ - stream_requests_per_sec: 5000
+ - delay_sec: 5
+ - timeout_sec: 10
+#
+# Enable SSL configuration (Disabled by default)
+#
+# - ssl:
+# - enabled: true
+# - keystore:
+# - path: "path/to/keystore.p12"
+# - password: password
+# - truststore:
+# - path: "path/to/truststore.p12"
+# - password: password
+
+
+healthcheck:
+ - poll_freq_millis: 30000
diff --git a/src/test/resources/sidecar_with_single_multiple_instances.yaml b/src/test/resources/sidecar_with_single_multiple_instances.yaml
new file mode 100644
index 0000000..3486886
--- /dev/null
+++ b/src/test/resources/sidecar_with_single_multiple_instances.yaml
@@ -0,0 +1,40 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra:
+ - host: localhost
+ - port: 9042
+ - data_dirs: /cassandra/d1/data, /cassandra/d2/data
+
+cassandra_instances:
+ - id: 1
+ host: localhost1
+ port: 9042
+ data_dirs: /cassandra/d1/data, /cassandra/d2/data
+ - id: 2
+ host: localhost2
+ port: 9042
+ data_dirs: /cassandra/d3/data, /cassandra/d4/data
+
+sidecar:
+ - host: 0.0.0.0
+ - port: 9043
+ - throttle:
+ - stream_requests_per_sec: 5000
+ - delay_sec: 5
+ - timeout_sec: 10
+#
+# Enable SSL configuration (Disabled by default)
+#
+# - ssl:
+# - enabled: true
+# - keystore:
+# - path: "path/to/keystore.p12"
+# - password: password
+# - truststore:
+# - path: "path/to/truststore.p12"
+# - password: password
+
+
+healthcheck:
+ - poll_freq_millis: 30000
\ No newline at end of file