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