CASSANDRASC-52: Sidecar returns own version in node settings

patch by Yuriy Semchyshyn; reviewed by Dinesh Joshi, Yifan Cai for CASSANDRASC-52
diff --git a/.gitignore b/.gitignore
index 07014fa..deaabc0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,9 @@
 src/dist/*
 *.logdir_IS_UNDEFINED
 
+# Sidecar version
+sidecar.version
+
 # Sidecar copyDist files copied to root directory
 agents
 bin
diff --git a/CHANGES.txt b/CHANGES.txt
index 73870a7..03259f2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Sidecar returns own version in node settings (CASSANDRASC-52)
  * Deprecate the sidecar cassandra health endpoint containing instance segment (CASSANDRASC-50)
  * Add an endpoint that gives information about the release version & partitioner name of a node (CASSANDRASC-48)
  * Introduce JMX foundation in Sidecar (CASSANDRASC-47)
diff --git a/build.gradle b/build.gradle
index e780572..87b4d73 100644
--- a/build.gradle
+++ b/build.gradle
@@ -78,10 +78,10 @@
 run {
     confFile = "file:" + File.separator + File.separator + "$projectDir/conf/sidecar.yaml"
     jvmArgs = ["-Dsidecar.logdir=./logs",
-                                 "-Dsidecar.config=" + confFile,
-                                 "-Dlogback.configurationFile=./conf/logback.xml",
-                                 "-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory",
-                                 "-javaagent:$projectDir/agents/jolokia-jvm-1.6.0-agent.jar=port=7777,host=localhost"]
+               "-Dsidecar.config=" + confFile,
+               "-Dlogback.configurationFile=./conf/logback.xml",
+               "-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory",
+               "-javaagent:$projectDir/agents/jolokia-jvm-1.6.0-agent.jar=port=7777,host=localhost"]
 }
 
 // add integration test
@@ -174,6 +174,14 @@
     integrationTestImplementation("org.testcontainers:junit-jupiter:${project.testcontainersVersion}")
 }
 
+jar {
+    doFirst {
+        // Store current Cassandra Sidecar build version in an embedded resource file;
+        // the file is either created or overwritten, and ignored by Git source control
+        new File("$projectDir/src/main/resources/sidecar.version").text = version
+    }
+}
+
 java {
     withJavadocJar()
     withSourcesJar()
diff --git a/cassandra40/src/main/java/org/apache/cassandra/sidecar/cassandra40/Cassandra40Factory.java b/cassandra40/src/main/java/org/apache/cassandra/sidecar/cassandra40/Cassandra40Factory.java
index 9064748..f93f074 100644
--- a/cassandra40/src/main/java/org/apache/cassandra/sidecar/cassandra40/Cassandra40Factory.java
+++ b/cassandra40/src/main/java/org/apache/cassandra/sidecar/cassandra40/Cassandra40Factory.java
@@ -43,11 +43,14 @@
 public class Cassandra40Factory implements ICassandraFactory
 {
     private static final Logger LOGGER = LoggerFactory.getLogger(Cassandra40Factory.class);
-    private final DnsResolver dnsResolver;
 
-    public Cassandra40Factory(DnsResolver dnsResolver)
+    private final DnsResolver dnsResolver;
+    private final String sidecarVersion;
+
+    public Cassandra40Factory(DnsResolver dnsResolver, String sidecarVersion)
     {
         this.dnsResolver = dnsResolver;
+        this.sidecarVersion = sidecarVersion;
     }
 
     /**
@@ -108,7 +111,8 @@
                                              .one();
 
                 return new NodeSettings(oneResult.getString("release_version"),
-                                        oneResult.getString("partitioner"));
+                                        oneResult.getString("partitioner"),
+                                        sidecarVersion);
             }
 
             /**
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/CassandraAdapterDelegate.java b/common/src/main/java/org/apache/cassandra/sidecar/common/CassandraAdapterDelegate.java
index 94708b8..d6bab9a 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/CassandraAdapterDelegate.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/CassandraAdapterDelegate.java
@@ -46,9 +46,10 @@
  */
 public class CassandraAdapterDelegate implements ICassandraAdapter, Host.StateListener
 {
+    private final String sidecarVersion;
+    private final CassandraVersionProvider versionProvider;
     private final CQLSessionProvider cqlSessionProvider;
     private final JmxClient jmxClient;
-    private final CassandraVersionProvider versionProvider;
     private SimpleCassandraVersion currentVersion;
     private ICassandraAdapter adapter;
     private volatile NodeSettings nodeSettings = null;
@@ -57,11 +58,13 @@
     private final AtomicBoolean registered = new AtomicBoolean(false);
     private final AtomicBoolean isHealthCheckActive = new AtomicBoolean(false);
 
-    public CassandraAdapterDelegate(CassandraVersionProvider provider,
+    public CassandraAdapterDelegate(CassandraVersionProvider versionProvider,
                                     CQLSessionProvider cqlSessionProvider,
-                                    JmxClient jmxClient)
+                                    JmxClient jmxClient,
+                                    String sidecarVersion)
     {
-        this.versionProvider = provider;
+        this.sidecarVersion = sidecarVersion;
+        this.versionProvider = versionProvider;
         this.cqlSessionProvider = cqlSessionProvider;
         this.jmxClient = jmxClient;
     }
@@ -126,10 +129,12 @@
 
             // Note that within the scope of this method, we should keep on using the local releaseVersion
             String releaseVersion = oneResult.getString("release_version");
-            NodeSettings newNodeSettings = new NodeSettings(releaseVersion, oneResult.getString("partitioner"));
+            NodeSettings newNodeSettings = new NodeSettings(releaseVersion,
+                                                            oneResult.getString("partitioner"),
+                                                            sidecarVersion);
             if (!newNodeSettings.equals(nodeSettings))
             {
-                // update the nodeSettings cache.
+                // Update the nodeSettings cache
                 SimpleCassandraVersion previousVersion = currentVersion;
                 currentVersion = SimpleCassandraVersion.create(releaseVersion);
                 adapter = versionProvider.cassandra(releaseVersion)
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/NodeSettings.java b/common/src/main/java/org/apache/cassandra/sidecar/common/NodeSettings.java
index b1c9634..ef5e9c8 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/NodeSettings.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/NodeSettings.java
@@ -16,9 +16,10 @@
  * limitations under the License.
  */
 
-
 package org.apache.cassandra.sidecar.common;
 
+import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -28,21 +29,40 @@
  */
 public class NodeSettings
 {
+    private static final String VERSION = "version";
+
     private final String releaseVersion;
     private final String partitioner;
+    private final Map<String, String> sidecar;
 
     /**
-     * Constructs a new {@link NodeSettings} object with the Cassandra node's release version and partitioner
-     * information.
+     * Constructs a new {@link NodeSettings} object with the Cassandra node's release version,
+     * partitioner, and Sidecar version information
      *
      * @param releaseVersion the release version of the Cassandra node
      * @param partitioner    the partitioner used by the Cassandra node
+     * @param sidecarVersion the version of the Sidecar on the Cassandra node
+     */
+    public NodeSettings(String releaseVersion, String partitioner, String sidecarVersion)
+    {
+        this(releaseVersion, partitioner, Collections.singletonMap(VERSION, sidecarVersion));
+    }
+
+    /**
+     * Constructs a new {@link NodeSettings} object with the Cassandra node's release version,
+     * partitioner, and Sidecar settings information
+     *
+     * @param releaseVersion the release version of the Cassandra node
+     * @param partitioner    the partitioner used by the Cassandra node
+     * @param sidecar        the settings of the Sidecar on the Cassandra node, including its version
      */
     public NodeSettings(@JsonProperty("releaseVersion") String releaseVersion,
-                        @JsonProperty("partitioner") String partitioner)
+                        @JsonProperty("partitioner")    String partitioner,
+                        @JsonProperty("sidecar")        Map<String, String> sidecar)
     {
         this.releaseVersion = releaseVersion;
-        this.partitioner = partitioner;
+        this.partitioner    = partitioner;
+        this.sidecar        = sidecar;
     }
 
     @JsonProperty("releaseVersion")
@@ -57,16 +77,34 @@
         return partitioner;
     }
 
+    @JsonProperty("sidecar")
+    public Map<String, String> sidecar()
+    {
+        return sidecar;
+    }
+
+    public String sidecarVersion()
+    {
+        return sidecar != null ? sidecar.get(VERSION) : "unknown";
+    }
+
     /**
      * {@inheritDoc}
      */
-    public boolean equals(Object o)
+    public boolean equals(Object other)
     {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        NodeSettings that = (NodeSettings) o;
-        return Objects.equals(releaseVersion, that.releaseVersion)
-               && Objects.equals(partitioner, that.partitioner);
+        if (this == other)
+        {
+            return true;
+        }
+        if (other == null || this.getClass() != other.getClass())
+        {
+            return false;
+        }
+        NodeSettings that = (NodeSettings) other;
+        return Objects.equals(this.releaseVersion, that.releaseVersion)
+            && Objects.equals(this.partitioner,    that.partitioner)
+            && Objects.equals(this.sidecar,        that.sidecar);
     }
 
     /**
@@ -74,6 +112,6 @@
      */
     public int hashCode()
     {
-        return Objects.hash(releaseVersion, partitioner);
+        return Objects.hash(releaseVersion, partitioner, sidecar);
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index fa0e196..e103113 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -22,8 +22,9 @@
 import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
-
 import com.google.common.util.concurrent.SidecarRateLimiter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
@@ -64,6 +65,7 @@
 import org.apache.cassandra.sidecar.routes.sstableuploads.SSTableUploadHandler;
 import org.apache.cassandra.sidecar.utils.ChecksumVerifier;
 import org.apache.cassandra.sidecar.utils.MD5ChecksumVerifier;
+import org.apache.cassandra.sidecar.utils.SidecarVersionProvider;
 import org.apache.cassandra.sidecar.utils.TimeProvider;
 import org.jboss.resteasy.plugins.server.vertx.VertxRegistry;
 import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
@@ -74,6 +76,8 @@
  */
 public class MainModule extends AbstractModule
 {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MainModule.class);
+
     public static final Map<String, String> OK_STATUS = Collections.singletonMap("status", "OK");
     public static final Map<String, String> NOT_OK_STATUS = Collections.singletonMap("status", "NOT_OK");
 
@@ -240,11 +244,11 @@
 
     @Provides
     @Singleton
-    public Configuration configuration(CassandraVersionProvider cassandraVersionProvider)
-    throws IOException
+    public Configuration configuration(CassandraVersionProvider cassandraVersionProvider,
+                                       SidecarVersionProvider sidecarVersionProvider) throws IOException
     {
         final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
-        return YAMLSidecarConfiguration.of(confPath, cassandraVersionProvider);
+        return YAMLSidecarConfiguration.of(confPath, cassandraVersionProvider, sidecarVersionProvider.sidecarVersion());
     }
 
     @Provides
@@ -263,10 +267,11 @@
 
     @Provides
     @Singleton
-    public CassandraVersionProvider cassandraVersionProvider(DnsResolver dnsResolver)
+    public CassandraVersionProvider cassandraVersionProvider(DnsResolver dnsResolver,
+                                                             SidecarVersionProvider sidecarVersionProvider)
     {
         CassandraVersionProvider.Builder builder = new CassandraVersionProvider.Builder();
-        builder.add(new Cassandra40Factory(dnsResolver));
+        builder.add(new Cassandra40Factory(dnsResolver, sidecarVersionProvider.sidecarVersion()));
         return builder.build();
     }
 
@@ -311,4 +316,11 @@
     {
         return new MD5ChecksumVerifier(vertx.fileSystem());
     }
+
+    @Provides
+    @Singleton
+    public SidecarVersionProvider sidecarVersionProvider()
+    {
+        return new SidecarVersionProvider("/sidecar.version");
+    }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java
index 500c283..c0aecb8 100644
--- a/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java
@@ -154,23 +154,32 @@
      *
      * @param confPath        the path to the Sidecar YAML configuration file
      * @param versionProvider a Cassandra version provider
+     * @param sidecarVersion  the version of the Sidecar from the current binary
      * @return the {@link YAMLConfiguration} parsed from the YAML file
      * @throws IOException when reading the configuration from file fails
      */
-    public static Configuration of(String confPath, CassandraVersionProvider versionProvider) throws IOException
+    public static Configuration of(String confPath,
+                                   CassandraVersionProvider versionProvider,
+                                   String sidecarVersion) throws IOException
     {
         YAMLConfiguration yamlConf = yamlConfiguration(confPath);
         int healthCheckFrequencyMillis = yamlConf.getInt(HEALTH_CHECK_INTERVAL, 1000);
         ValidationConfiguration validationConfiguration = validationConfiguration(yamlConf);
-        InstancesConfig instancesConfig = instancesConfig(yamlConf, versionProvider, healthCheckFrequencyMillis);
-        CacheConfiguration ssTableImportCacheConfiguration = cacheConfig(yamlConf, SSTABLE_IMPORT_CACHE_CONFIGURATION,
+        InstancesConfig instancesConfig = instancesConfig(yamlConf,
+                                                          versionProvider,
+                                                          healthCheckFrequencyMillis,
+                                                          sidecarVersion);
+        CacheConfiguration ssTableImportCacheConfiguration = cacheConfig(yamlConf,
+                                                                         SSTABLE_IMPORT_CACHE_CONFIGURATION,
                                                                          TimeUnit.HOURS.toMillis(2),
                                                                          10_000);
-        WorkerPoolConfiguration serverWorkerPoolConf = workerPoolConfiguration(yamlConf, WORKER_POOL_FOR_SERVICE,
+        WorkerPoolConfiguration serverWorkerPoolConf = workerPoolConfiguration(yamlConf,
+                                                                               WORKER_POOL_FOR_SERVICE,
                                                                                "sidecar-worker-pool",
                                                                                VertxOptions.DEFAULT_WORKER_POOL_SIZE,
                                                                                TimeUnit.SECONDS.toMillis(60));
-        WorkerPoolConfiguration internalWorkerPoolConf = workerPoolConfiguration(yamlConf, WORKER_POOL_FOR_INTERNAL,
+        WorkerPoolConfiguration internalWorkerPoolConf = workerPoolConfiguration(yamlConf,
+                                                                                 WORKER_POOL_FOR_INTERNAL,
                                                                                  "sidecar-internal-worker-pool",
                                                                                  VertxOptions.DEFAULT_WORKER_POOL_SIZE,
                                                                                  TimeUnit.SECONDS.toMillis(60));
@@ -231,10 +240,13 @@
      * @param yamlConf                   the object used to parse the YAML file
      * @param versionProvider            a Cassandra version provider
      * @param healthCheckFrequencyMillis the health check frequency configuration in milliseconds
+     * @param sidecarVersion             the version of the Sidecar from the current binary
      * @return the parsed {@link InstancesConfig} from the {@code yamlConf} object
      */
-    private static InstancesConfig instancesConfig(YAMLConfiguration yamlConf, CassandraVersionProvider versionProvider,
-                                                   int healthCheckFrequencyMillis)
+    private static InstancesConfig instancesConfig(YAMLConfiguration yamlConf,
+                                                   CassandraVersionProvider versionProvider,
+                                                   int healthCheckFrequencyMillis,
+                                                   String sidecarVersion)
     {
         /* Since we are supporting handling multiple instances in Sidecar optionally, we prefer reading single instance
          * data over reading multiple instances section
@@ -244,7 +256,8 @@
         {
             InstanceMetadata instanceMetadata = buildInstanceMetadata(singleInstanceConf,
                                                                       versionProvider,
-                                                                      healthCheckFrequencyMillis);
+                                                                      healthCheckFrequencyMillis,
+                                                                      sidecarVersion);
             return new InstancesConfigImpl(instanceMetadata);
         }
 
@@ -254,7 +267,8 @@
         {
             InstanceMetadata instanceMetadata = buildInstanceMetadata(instance,
                                                                       versionProvider,
-                                                                      healthCheckFrequencyMillis);
+                                                                      healthCheckFrequencyMillis,
+                                                                      sidecarVersion);
             instanceMetas.add(instanceMetadata);
         }
         return new InstancesConfigImpl(instanceMetas);
@@ -313,11 +327,13 @@
      * @param instance                   the object that allows reading from the YAML file
      * @param versionProvider            a Cassandra version provider
      * @param healthCheckFrequencyMillis the health check frequency configuration in milliseconds
+     * @param sidecarVersion             the version of the Sidecar from the current binary
      * @return the parsed {@link InstanceMetadata} from YAML
      */
     private static InstanceMetadata buildInstanceMetadata(org.apache.commons.configuration2.Configuration instance,
                                                           CassandraVersionProvider versionProvider,
-                                                          int healthCheckFrequencyMillis)
+                                                          int healthCheckFrequencyMillis,
+                                                          String sidecarVersion)
     {
         int id = instance.get(Integer.class, CASSANDRA_INSTANCE_ID, 1);
         String host = instance.get(String.class, CASSANDRA_INSTANCE_HOST);
@@ -339,6 +355,7 @@
                                         stagingDir,
                                         session,
                                         jmxClient,
-                                        versionProvider);
+                                        versionProvider,
+                                        sidecarVersion);
     }
 }
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
index 02ecb2f..a41a152 100644
--- a/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
@@ -47,10 +47,11 @@
                                 String stagingDir,
                                 CQLSessionProvider sessionProvider,
                                 JmxClient jmxClient,
-                                CassandraVersionProvider versionProvider)
+                                CassandraVersionProvider versionProvider,
+                                String sidecarVersion)
     {
         this(id, host, port, dataDirs, stagingDir,
-             new CassandraAdapterDelegate(versionProvider, sessionProvider, jmxClient));
+             new CassandraAdapterDelegate(versionProvider, sessionProvider, jmxClient, sidecarVersion));
     }
 
     @VisibleForTesting
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/SidecarVersionProvider.java b/src/main/java/org/apache/cassandra/sidecar/utils/SidecarVersionProvider.java
new file mode 100644
index 0000000..9279895
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/SidecarVersionProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Retrieves, caches, and provides build version of this Sidecar binary
+ */
+public class SidecarVersionProvider
+{
+    private final String sidecarVersion;
+
+    public SidecarVersionProvider(String resource)
+    {
+        try (InputStream input = getClass().getResourceAsStream(resource);
+             ByteArrayOutputStream output = new ByteArrayOutputStream())
+        {
+            byte[] buffer = new byte[32];
+            int length;
+            while ((length = input.read(buffer)) >= 0)
+            {
+                output.write(buffer, 0, length);
+            }
+            sidecarVersion = output.toString(StandardCharsets.UTF_8.name());
+        }
+        catch (Exception exception)
+        {
+            throw new IllegalStateException("Failed to retrieve Sidecar version from resource " + resource, exception);
+        }
+    }
+
+    public String sidecarVersion()
+    {
+        return sidecarVersion;
+    }
+}
diff --git a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
index 0b2b1c2..ccdd20e 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
@@ -33,6 +33,7 @@
 import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 import org.apache.cassandra.sidecar.config.CacheConfiguration;
 import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.utils.SidecarVersionProvider;
 
 /**
  * Provides the basic dependencies for integration tests
@@ -48,7 +49,8 @@
 
     @Provides
     @Singleton
-    public InstancesConfig instancesConfig(CassandraVersionProvider versionProvider)
+    public InstancesConfig instancesConfig(CassandraVersionProvider versionProvider,
+                                           SidecarVersionProvider sidecarVersionProvider)
     {
         String dataDirectory = cassandraTestContext.dataDirectoryPath.toFile().getAbsolutePath();
         String stagingDirectory = cassandraTestContext.dataDirectoryPath.resolve("staging")
@@ -60,7 +62,8 @@
                                                              stagingDirectory,
                                                              cassandraTestContext.session,
                                                              cassandraTestContext.jmxClient,
-                                                             versionProvider);
+                                                             versionProvider,
+                                                             sidecarVersionProvider.sidecarVersion());
         return new InstancesConfigImpl(metadata);
     }
 
diff --git a/src/test/integration/org/apache/cassandra/sidecar/common/DelegateTest.java b/src/test/integration/org/apache/cassandra/sidecar/common/DelegateTest.java
index 4f1f8f2..bcb081c 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/common/DelegateTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/common/DelegateTest.java
@@ -36,7 +36,8 @@
     void testCorrectVersionIsEnabled(CassandraTestContext context)
     {
         CassandraVersionProvider provider = new CassandraVersionProvider.Builder().add(new V30()).build();
-        CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(provider, context.session, context.jmxClient);
+        CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(
+                provider, context.session, context.jmxClient, "1.0-TEST");
         SimpleCassandraVersion version = delegate.version();
         assertThat(version).isNotNull();
     }
@@ -45,7 +46,8 @@
     void testHealthCheck(CassandraTestContext context) throws IOException, InterruptedException
     {
         CassandraVersionProvider provider = new CassandraVersionProvider.Builder().add(new V30()).build();
-        CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(provider, context.session, context.jmxClient);
+        CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(
+                provider, context.session, context.jmxClient, "1.0-TEST");
 
         delegate.healthCheck();
 
diff --git a/src/test/integration/org/apache/cassandra/sidecar/common/testing/TestVersionSupplier.java b/src/test/integration/org/apache/cassandra/sidecar/common/testing/TestVersionSupplier.java
index 269fbc5..92a9f7d 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/common/testing/TestVersionSupplier.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/common/testing/TestVersionSupplier.java
@@ -39,7 +39,7 @@
 {
     Stream<TestVersion> testVersions()
     {
-        return Stream.of(new TestVersion("4.0.7", new Cassandra40Factory(DnsResolver.DEFAULT), "cassandra:4.0"));
+        return Stream.of(
+                new TestVersion("4.0.7", new Cassandra40Factory(DnsResolver.DEFAULT, "1.0-TEST"), "cassandra:4.0"));
     }
-
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/MainModuleTest.java b/src/test/java/org/apache/cassandra/sidecar/MainModuleTest.java
new file mode 100644
index 0000000..27bd378
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/MainModuleTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar;
+
+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.utils.SidecarVersionProvider;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Unit tests for the {@link MainModule} class
+ */
+public class MainModuleTest
+{
+    @Test
+    public void testSidecarVersion()
+    {
+        Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
+        SidecarVersionProvider sidecarVersionProvider = injector.getInstance(SidecarVersionProvider.class);
+
+        String sidecarVersion = sidecarVersionProvider.sidecarVersion();
+
+        assertEquals("1.0-TEST", sidecarVersion);
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestModule.java b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 6e26964..1573972 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -63,8 +63,7 @@
 
     protected Configuration abstractConfig(InstancesConfig instancesConfig)
     {
-        WorkerPoolConfiguration workPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                           30000);
+        WorkerPoolConfiguration workPoolConf = new WorkerPoolConfiguration("test-pool", 10, 30000);
         return new Configuration.Builder()
                .setInstancesConfig(instancesConfig)
                .setHost("127.0.0.1")
@@ -115,7 +114,8 @@
         CassandraAdapterDelegate delegate = mock(CassandraAdapterDelegate.class);
         if (isUp)
         {
-            when(delegate.nodeSettings()).thenReturn(new NodeSettings("testVersion", "testPartitioner"));
+            when(delegate.nodeSettings()).thenReturn(new NodeSettings(
+                    "testVersion", "testPartitioner", Collections.singletonMap("version", "testSidecar")));
         }
         when(delegate.isUp()).thenReturn(isUp);
         when(instanceMeta.delegate()).thenReturn(delegate);
diff --git a/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java
index 2507157..82dbc68 100644
--- a/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java
@@ -37,26 +37,27 @@
  */
 class YAMLSidecarConfigurationTest
 {
-    CassandraVersionProvider versionProvider = mock(CassandraVersionProvider.class);
+    private static final String SIDECAR_VERSION = "unknown";
+
+    private final CassandraVersionProvider versionProvider = mock(CassandraVersionProvider.class);
 
     @Test
     public void testSidecarConfiguration() throws IOException
     {
-        Configuration multipleInstancesConfig =
-        YAMLSidecarConfiguration.of(confPath("sidecar_multiple_instances.yaml"),
-                                    versionProvider);
+        Configuration multipleInstancesConfig = YAMLSidecarConfiguration.of(
+                confPath("sidecar_multiple_instances.yaml"), versionProvider, SIDECAR_VERSION);
         validateSidecarConfiguration(multipleInstancesConfig);
 
-        Configuration singleInstanceConfig =
-        YAMLSidecarConfiguration.of(confPath("sidecar_single_instance.yaml"), versionProvider);
+        Configuration singleInstanceConfig = YAMLSidecarConfiguration.of(
+                confPath("sidecar_single_instance.yaml"), versionProvider, SIDECAR_VERSION);
         validateSidecarConfiguration(singleInstanceConfig);
     }
 
     @Test
     public void testLegacySidecarYAMLFormatWithSingleInstance() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_single_instance.yaml"), versionProvider);
+        Configuration configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_single_instance.yaml"), versionProvider, SIDECAR_VERSION);
         InstancesConfig instancesConfig = configuration.getInstancesConfig();
         assertThat(instancesConfig.instances().size()).isEqualTo(1);
         InstanceMetadata instanceMetadata = instancesConfig.instances().get(0);
@@ -67,21 +68,20 @@
     @Test
     public void testReadAllowableTimeSkew() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_single_instance.yaml"), versionProvider);
+        Configuration configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_single_instance.yaml"), versionProvider, SIDECAR_VERSION);
         assertThat(configuration.allowableSkewInMinutes()).isEqualTo(89);
 
-        configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_custom_allowable_time_skew.yaml"), versionProvider);
+        configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_custom_allowable_time_skew.yaml"), versionProvider, SIDECAR_VERSION);
         assertThat(configuration.allowableSkewInMinutes()).isEqualTo(1);
     }
 
     @Test
     public void testReadingSingleInstanceSectionOverMultipleInstances() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_with_single_multiple_instances.yaml"),
-                                    versionProvider);
+        Configuration configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_with_single_multiple_instances.yaml"), versionProvider, SIDECAR_VERSION);
         InstancesConfig instancesConfig = configuration.getInstancesConfig();
         assertThat(instancesConfig.instances().size()).isEqualTo(1);
         InstanceMetadata instanceMetadata = instancesConfig.instances().get(0);
@@ -92,9 +92,8 @@
     @Test
     public void testReadingMultipleInstances() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_multiple_instances.yaml"),
-                                    versionProvider);
+        Configuration configuration =  YAMLSidecarConfiguration.of(
+                confPath("sidecar_multiple_instances.yaml"), versionProvider, SIDECAR_VERSION);
         InstancesConfig instancesConfig = configuration.getInstancesConfig();
         assertThat(instancesConfig.instances().size()).isEqualTo(2);
     }
@@ -102,9 +101,8 @@
     @Test
     public void testReadingCassandraInputValidation() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_validation_configuration.yaml"),
-                                    versionProvider);
+        Configuration configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_validation_configuration.yaml"), versionProvider, SIDECAR_VERSION);
         ValidationConfiguration validationConfiguration = configuration.getValidationConfiguration();
 
         assertThat(validationConfiguration.forbiddenKeyspaces()).contains("a", "b", "c");
@@ -118,9 +116,8 @@
     @Test
     public void testUploadsConfiguration() throws IOException
     {
-        Configuration configuration =
-        YAMLSidecarConfiguration.of(confPath("sidecar_multiple_instances.yaml"),
-                                    versionProvider);
+        Configuration configuration = YAMLSidecarConfiguration.of(
+                confPath("sidecar_multiple_instances.yaml"), versionProvider, SIDECAR_VERSION);
 
         assertThat(configuration.getConcurrentUploadsLimit()).isEqualTo(80);
         assertThat(configuration.getMinSpacePercentRequiredForUpload()).isEqualTo(10);
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
index a6d5a6c..e3cdce8 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
@@ -93,6 +93,7 @@
                   NodeSettings status = resp.result().bodyAsJson(NodeSettings.class);
                   assertThat(status.partitioner()).isEqualTo("testPartitioner");
                   assertThat(status.releaseVersion()).isEqualTo("testVersion");
+                  assertThat(status.sidecarVersion()).isEqualTo("testSidecar");
                   context.completeNow();
               });
     }
@@ -108,6 +109,7 @@
                   NodeSettings status = resp.result().bodyAsJson(NodeSettings.class);
                   assertThat(status.partitioner()).isEqualTo("testPartitioner");
                   assertThat(status.releaseVersion()).isEqualTo("testVersion");
+                  assertThat(status.sidecarVersion()).isEqualTo("testSidecar");
                   context.completeNow();
               });
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
index b17eb0e..020b0e2 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
+++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
@@ -101,12 +101,12 @@
 
         if (delegate1 == null)
         {
-            delegate1 = new CassandraAdapterDelegate(versionProvider, cqlSessionProvider1, null);
+            delegate1 = new CassandraAdapterDelegate(versionProvider, cqlSessionProvider1, null, null);
         }
 
         if (delegate2 == null)
         {
-            delegate2 = new CassandraAdapterDelegate(versionProvider, cqlSessionProvider2, null);
+            delegate2 = new CassandraAdapterDelegate(versionProvider, cqlSessionProvider2, null, null);
         }
 
         InstanceMetadataImpl localhost = new InstanceMetadataImpl(1,