This is an automated email from the ASF dual-hosted git repository.

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 42359c1c CASSSIDECAR-192: Fix CassandraInstance not found error (#180)
42359c1c is described below

commit 42359c1c7c390b1e4e31e744128c017bfc393c1c
Author: Yifan Cai <[email protected]>
AuthorDate: Mon Feb 3 12:26:20 2025 -0800

    CASSSIDECAR-192: Fix CassandraInstance not found error (#180)
    
    Patch by Yifan Cai; Reviewed by Francisco Guerrero for CASSSIDECAR-192
---
 .circleci/config.yml                               |   6 +-
 CHANGES.txt                                        |   1 +
 build.gradle                                       |  27 +++-
 gradle/common/integrationTestTask.gradle           |   1 -
 .../sidecar/cluster/InstancesMetadata.java         |  14 +-
 .../sidecar/cluster/InstancesMetadataImpl.java     | 123 ++++++++++++----
 .../sidecar/cluster/instance/InstanceMetadata.java |  19 ++-
 .../cluster/instance/InstanceMetadataImpl.java     |  43 +++++-
 ....java => NoSuchCassandraInstanceException.java} |  12 +-
 .../sidecar/metrics/SidecarMetricsImpl.java        |   4 +-
 .../cassandra/sidecar/routes/AbstractHandler.java  |   4 +-
 .../sidecar/utils/InstanceMetadataFetcher.java     |  20 +--
 .../sidecar/testing/IntegrationTestModule.java     |  10 +-
 .../sidecar/cluster/InstancesMetadataImplTest.java | 161 +++++++++++++++++++++
 .../cluster/instance/InstanceMetadataImplTest.java |  55 ++++++-
 15 files changed, 426 insertions(+), 74 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 973505f2..6bf814de 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -121,7 +121,7 @@ jobs:
       - attach_workspace:
           at: dtest-jars
       - run: ./scripts/install-shaded-dtest-jar-local.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=4.1.8 
-Dcassandra.sidecar.versions_to_test="4.0" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTestLightWeight --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=4.1.8 
-Dcassandra.sidecar.versions_to_test="4.0" checkstyleIntegrationTest 
integrationTestLightWeight --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -173,7 +173,7 @@ jobs:
       - attach_workspace:
           at: dtest-jars
       - run: ./scripts/install-shaded-dtest-jar-local.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=5.0.3 
-Dcassandra.sidecar.versions_to_test="5.0" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTestLightWeight --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=5.0.3 
-Dcassandra.sidecar.versions_to_test="5.0" checkstyleIntegrationTest 
integrationTestLightWeight --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -225,7 +225,7 @@ jobs:
       - attach_workspace:
           at: dtest-jars
       - run: ./scripts/install-shaded-dtest-jar-local.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=5.1 
-Dcassandra.sidecar.versions_to_test="5.1" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTestLightWeight --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=5.1 
-Dcassandra.sidecar.versions_to_test="5.1" checkstyleIntegrationTest 
integrationTestLightWeight --stacktrace
 
       - store_artifacts:
           path: build/reports
diff --git a/CHANGES.txt b/CHANGES.txt
index 6cc3557e..b5177189 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Fix Cassandra instance not found error (CASSSIDECAR-192)
  * Implemented Schema Reporter for Integration with DataHub (CASSSIDECAR-191)
  * Add sidecar endpoint to retrieve stream stats (CASSSIDECAR-180)
  * Add sidecar endpoint to retrieve cassandra gossip health (CASSSIDECAR-173)
diff --git a/build.gradle b/build.gradle
index f04390d2..599f90b1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,7 +63,21 @@ def codeCheckTasks = task("codeCheckTasks")
 allprojects {
     apply plugin: 'jacoco'
     apply plugin: 'checkstyle'
-    apply plugin: "com.github.spotbugs"
+
+    // do not run spot-bug on test-specific sub-projects
+    if (!it.name.startsWith('integration-') && !it.name.startsWith("test-"))
+    {
+        apply plugin: "com.github.spotbugs"
+        spotbugs {
+            toolVersion = '4.2.3'
+            excludeFilter = file("${project.rootDir}/spotbugs-exclude.xml")
+        }
+        codeCheckTasks.dependsOn(tasks.withType(SpotBugsTask))
+        tasks.withType(Test) {
+            // define the order of the tasks, if SpotBugsTask runs
+            shouldRunAfter(tasks.withType(SpotBugsTask))
+        }
+    }
 
     repositories {
         mavenCentral()
@@ -76,20 +90,17 @@ allprojects {
         toolVersion '7.8.1'
         configFile file("${project.rootDir}/checkstyle.xml")
     }
-    spotbugs {
-        toolVersion = '4.2.3'
-        excludeFilter = file("${project.rootDir}/spotbugs-exclude.xml")
-    }
 
+    // must run the dependant tasks in order to run codeCheckTasks
+    codeCheckTasks.dependsOn(tasks.withType(Javadoc))
     codeCheckTasks.dependsOn(tasks.withType(Checkstyle))
     codeCheckTasks.dependsOn(tasks.withType(RatTask))
-    codeCheckTasks.dependsOn(tasks.withType(SpotBugsTask))
 
     tasks.withType(Test) {
+        // define the order of the tasks, if the below tasks runs
         shouldRunAfter(codeCheckTasks)
         shouldRunAfter(tasks.withType(Checkstyle))
         shouldRunAfter(tasks.withType(RatTask))
-        shouldRunAfter(tasks.withType(SpotBugsTask))
     }
 }
 
@@ -315,7 +326,7 @@ rat {
 installDist.dependsOn copyJolokia
 check.dependsOn codeCheckTasks
 if (JavaVersion.current().isJava11Compatible()) {
-    build.dependsOn copyJolokia, copyDocs
+    build.dependsOn codeCheckTasks, copyJolokia, copyDocs
 }
 run.dependsOn build
 
diff --git a/gradle/common/integrationTestTask.gradle 
b/gradle/common/integrationTestTask.gradle
index f5bdab8b..38547b61 100644
--- a/gradle/common/integrationTestTask.gradle
+++ b/gradle/common/integrationTestTask.gradle
@@ -95,4 +95,3 @@ task("integrationTest").dependsOn integrationTestLightWeight, 
integrationTestHea
 
 compileIntegrationTestJava.onlyIf { "true" != 
System.getenv("skipIntegrationTest") }
 checkstyleIntegrationTest.onlyIf { "true" != 
System.getenv("skipIntegrationTest") }
-spotbugsIntegrationTest.onlyIf { "true" != 
System.getenv("skipIntegrationTest") }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadata.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadata.java
index f9d91c9d..a8bcf1c4 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadata.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadata.java
@@ -21,7 +21,7 @@ package org.apache.cassandra.sidecar.cluster;
 import java.util.List;
 
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -43,16 +43,18 @@ public interface InstancesMetadata
      *
      * @param id instance's id
      * @return instance meta information
-     * @throws NoSuchSidecarInstanceException when the instance with {@code 
id} does not exist
+     * @throws NoSuchCassandraInstanceException when the instance with {@code 
id} does not exist
      */
-    InstanceMetadata instanceFromId(int id) throws 
NoSuchSidecarInstanceException;
+    @NotNull
+    InstanceMetadata instanceFromId(int id) throws 
NoSuchCassandraInstanceException;
 
     /**
      * Lookup instance metadata by host name.
      *
-     * @param host host address of instance
+     * @param hostOrIpAddress hostname or IP address of instance
      * @return instance meta information
-     * @throws NoSuchSidecarInstanceException when the instance for {@code 
host} does not exist
+     * @throws NoSuchCassandraInstanceException when the instance for {@code 
host} does not exist
      */
-    InstanceMetadata instanceFromHost(String host) throws 
NoSuchSidecarInstanceException;
+    @NotNull
+    InstanceMetadata instanceFromHost(String hostOrIpAddress) throws 
NoSuchCassandraInstanceException;
 }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImpl.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImpl.java
index 6a42e915..3016b214 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImpl.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImpl.java
@@ -20,14 +20,18 @@ package org.apache.cassandra.sidecar.cluster;
 
 import java.net.UnknownHostException;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -35,10 +39,15 @@ import org.jetbrains.annotations.NotNull;
  */
 public class InstancesMetadataImpl implements InstancesMetadata
 {
-    private final Map<Integer, InstanceMetadata> idToInstanceMetadata;
-    private final Map<String, InstanceMetadata> hostToInstanceMetadata;
-    private final List<InstanceMetadata> instanceMetadataList;
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(InstancesMetadataImpl.class);
+    private static final long ONE_SECOND = TimeUnit.SECONDS.toMillis(1);
+
+    private final AtomicLong lastUpdateTimestamp;
     private final DnsResolver dnsResolver;
+    private final List<InstanceMetadata> instanceMetadataList;
+    private final Map<Integer, InstanceMetadata> idToInstanceMetadata;
+    private final Map<String, InstanceMetadata> hostNameToInstanceMetadata;
+    private volatile Map<String, InstanceMetadata> ipToInstanceMetadata;
 
     public InstancesMetadataImpl(InstanceMetadata instanceMetadata, 
DnsResolver dnsResolver)
     {
@@ -47,14 +56,22 @@ public class InstancesMetadataImpl implements 
InstancesMetadata
 
     public InstancesMetadataImpl(List<InstanceMetadata> instanceMetadataList, 
DnsResolver dnsResolver)
     {
-        this.idToInstanceMetadata = instanceMetadataList.stream()
-                                                        
.collect(Collectors.toMap(InstanceMetadata::id,
-                                                                               
   Function.identity()));
-        this.hostToInstanceMetadata = instanceMetadataList.stream()
-                                                          
.collect(Collectors.toMap(InstanceMetadata::host,
-                                                                               
     Function.identity()));
-        this.instanceMetadataList = instanceMetadataList;
         this.dnsResolver = dnsResolver;
+        this.instanceMetadataList = instanceMetadataList;
+        this.idToInstanceMetadata = new HashMap<>(instanceMetadataList.size());
+        this.ipToInstanceMetadata = new HashMap<>(instanceMetadataList.size());
+        this.hostNameToInstanceMetadata = new 
HashMap<>(instanceMetadataList.size());
+        for (InstanceMetadata instanceMetadata : instanceMetadataList)
+        {
+            this.idToInstanceMetadata.put(instanceMetadata.id(), 
instanceMetadata);
+            if (instanceMetadata.ipAddress() != null)
+            {
+                this.ipToInstanceMetadata.put(instanceMetadata.ipAddress(), 
instanceMetadata);
+            }
+            // 'host' could be IP already, in such case, 
hostNameToInstanceMetadata map is identical to ipToInstanceMetadata
+            this.hostNameToInstanceMetadata.put(instanceMetadata.host(), 
instanceMetadata);
+        }
+        this.lastUpdateTimestamp = new AtomicLong(0); // The field stores the 
timestamp value, but let's not query system clock for initialization
     }
 
     @Override
@@ -64,37 +81,91 @@ public class InstancesMetadataImpl implements 
InstancesMetadata
     }
 
     @Override
-    public InstanceMetadata instanceFromId(int id) throws 
NoSuchSidecarInstanceException
+    public InstanceMetadata instanceFromId(int id) throws 
NoSuchCassandraInstanceException
     {
         InstanceMetadata instanceMetadata = idToInstanceMetadata.get(id);
         if (instanceMetadata == null)
         {
-            throw new NoSuchSidecarInstanceException("Instance id '" + id + "' 
not found");
+            throw new NoSuchCassandraInstanceException("Instance id '" + id + 
"' not found");
         }
         return instanceMetadata;
     }
 
     @Override
-    public InstanceMetadata instanceFromHost(String host) throws 
NoSuchSidecarInstanceException
+    public InstanceMetadata instanceFromHost(String hostOrIpAddress) throws 
NoSuchCassandraInstanceException
     {
-        InstanceMetadata instanceMetadata = hostToInstanceMetadata.get(host);
-        if (instanceMetadata == null)
+        // if the input string is hostname string, resolve the ip address and 
loop up again
+        InstanceMetadata instanceMetadata = 
hostNameToInstanceMetadata.get(hostOrIpAddress);
+        if (instanceMetadata != null)
+        {
+            return instanceMetadata;
+        }
+
+        // the input string is probably IP address string
+        instanceMetadata = ipToInstanceMetadata.get(hostOrIpAddress);
+        if (instanceMetadata != null)
+        {
+            return instanceMetadata;
+        }
+
+        // maybe the input string is host, try to resolve it
+        String ipAddress = hostOrIpAddress;
+        try
         {
+            ipAddress = dnsResolver.resolve(hostOrIpAddress);
+        }
+        catch (UnknownHostException e)
+        {
+            // Do not exit; try to look up again after updating the 
IpToInstanceMetadata map
+            LOGGER.debug("Failed to resolve IP address for {}", 
hostOrIpAddress);
+        }
+
+        // maybe the IP of the hostname has been updated in the DNS, and 
client is using the updated IP
+        // update the ipToInstanceMetadata and look up with the IP address 
again
+        maybeUpdateIpToInstanceMetadata();
+        instanceMetadata = ipToInstanceMetadata.get(ipAddress);
+        if (instanceMetadata != null)
+        {
+            return instanceMetadata;
+        }
+
+        throw new NoSuchCassandraInstanceException("Instance with host address 
'" + hostOrIpAddress + "' not found");
+    }
+
+    // Update the IpToInstanceMetadata if it has been enough time since the 
last update
+    //
+    // Memory visibility effects:
+    // The method does not guarantee the caller see the latest value in the 
IpToInstanceMetadata when the method exits.
+    // However, once the reference of IpToInstanceMetadata is updated, the 
subsequent procedures should see the updated value
+    private void maybeUpdateIpToInstanceMetadata()
+    {
+        long now = System.currentTimeMillis();
+        long lastValue = lastUpdateTimestamp.get();
+        if (now - lastValue <= ONE_SECOND // update at most once a second
+            || !lastUpdateTimestamp.compareAndSet(lastValue, now)) // another 
thread has updated the timestamp. let it update the map
+        {
+            return;
+        }
+
+        Map<String, InstanceMetadata> updated = new 
HashMap<>(instanceMetadataList.size());
+        for (InstanceMetadata instanceMetadata : instanceMetadataList)
+        {
+            String ipAddress = instanceMetadata.ipAddress(); // existing ip
             try
             {
-                instanceMetadata = 
hostToInstanceMetadata.get(dnsResolver.resolve(host));
+                ipAddress = instanceMetadata.refreshIpAddress(); // updated ip
             }
-            catch (UnknownHostException cause)
+            catch (UnknownHostException uhe)
             {
-                throw new NoSuchSidecarInstanceException("Instance with host 
address '" + host + "' not found, "
-                                                         + "and an error 
occurred when attempting to resolve its "
-                                                         + "IP address.", 
cause);
+                // log a warning and continue; Do not update the ipAddress for 
this instance
+                LOGGER.warn("Failed to resolve IP address from host. Going to 
use the existing IP address. host={}",
+                            instanceMetadata.host(), uhe);
             }
-            if (instanceMetadata == null)
+            if (ipAddress != null)
             {
-                throw new NoSuchSidecarInstanceException("Instance with host 
address '" + host + "' not found");
+                updated.put(ipAddress, instanceMetadata);
             }
         }
-        return instanceMetadata;
+        this.ipToInstanceMetadata = updated;
     }
 }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
index cb7065ee..5abcea8b 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadata.java
@@ -18,6 +18,7 @@
 
 package org.apache.cassandra.sidecar.cluster.instance;
 
+import java.net.UnknownHostException;
 import java.util.List;
 
 import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
@@ -37,10 +38,26 @@ public interface InstanceMetadata
     int id();
 
     /**
-     * @return the host address of the Cassandra instance
+     * @return the hostname or IP address of the Cassandra instance
      */
     String host();
 
+    /**
+     * @return the IP address of the Cassandra instance. When no IP address is 
resolved, it returns null
+     */
+    @Nullable
+    String ipAddress();
+
+    /**
+     * Resolve the ipAddress and update.
+     * @return the IP address resolved
+     * @throws UnknownHostException when hostname cannot be resolved to IP 
address
+     */
+    default String refreshIpAddress() throws UnknownHostException
+    {
+        return ipAddress();
+    }
+
     /**
      * @return the native transport port number of the Cassandra instance
      */
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
index 7fc79413..729bc8d4 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.sidecar.cluster.instance;
 
 import java.io.File;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -28,6 +29,7 @@ import java.util.stream.Collectors;
 import com.codahale.metrics.MetricRegistry;
 import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.DataObjectBuilder;
+import org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
 import org.apache.cassandra.sidecar.common.utils.Preconditions;
 import org.apache.cassandra.sidecar.exceptions.CassandraUnavailableException;
 import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
@@ -63,11 +65,15 @@ public class InstanceMetadataImpl implements 
InstanceMetadata
     @Nullable
     private final CassandraAdapterDelegate delegate;
     private final InstanceMetrics metrics;
+    private final DnsResolver dnsResolver;
+    private volatile String ipAddress;
 
     protected InstanceMetadataImpl(Builder builder)
     {
         id = builder.id;
         host = builder.host;
+        dnsResolver = builder.dnsResolver;
+        ipAddress = builder.ipAddress;
         port = builder.port;
         delegate = builder.delegate;
         metrics = builder.metrics;
@@ -96,6 +102,20 @@ public class InstanceMetadataImpl implements 
InstanceMetadata
         return host;
     }
 
+    @Nullable
+    @Override
+    public String ipAddress()
+    {
+        return ipAddress;
+    }
+
+    @Override
+    public String refreshIpAddress() throws UnknownHostException
+    {
+        this.ipAddress = dnsResolver.resolve(host);
+        return ipAddress;
+    }
+
     @Override
     public int port()
     {
@@ -187,8 +207,10 @@ public class InstanceMetadataImpl implements 
InstanceMetadata
      */
     public static class Builder implements DataObjectBuilder<Builder, 
InstanceMetadataImpl>
     {
+        protected DnsResolver dnsResolver;
         protected Integer id;
         protected String host;
+        protected String ipAddress;
         protected int port;
         protected String storageDir;
         protected List<String> dataDirs;
@@ -210,6 +232,7 @@ public class InstanceMetadataImpl implements 
InstanceMetadata
         {
             id = instanceMetadata.id;
             host = instanceMetadata.host;
+            ipAddress = instanceMetadata.ipAddress;
             port = instanceMetadata.port;
             dataDirs = new ArrayList<>(instanceMetadata.dataDirs);
             stagingDir = instanceMetadata.stagingDir;
@@ -240,14 +263,30 @@ public class InstanceMetadataImpl implements 
InstanceMetadata
         }
 
         /**
-         * Sets the {@code host} and returns a reference to this Builder 
enabling method chaining.
+         * Sets the {@code host} and the {@code ipAddress} resolved by {@link 
DnsResolver#DEFAULT}
+         * and returns a reference to this Builder enabling method chaining.
          *
          * @param host the {@code host} to set
          * @return a reference to this Builder
          */
         public Builder host(String host)
         {
-            return update(b -> b.host = host);
+            return host(host, DnsResolver.DEFAULT);
+        }
+
+        /**
+         * Sets the {@code host} and the {@code ipAddress} resolved by 
dnsResolver
+         * and returns a reference to this Builder enabling method chaining.
+         *
+         * @param host the {@code host} to set
+         * @return a reference to this Builder
+         */
+        public Builder host(String host, DnsResolver dnsResolver)
+        {
+            return update(b -> {
+                b.host = host;
+                b.dnsResolver = dnsResolver;
+            });
         }
 
         /**
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchSidecarInstanceException.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchCassandraInstanceException.java
similarity index 78%
rename from 
server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchSidecarInstanceException.java
rename to 
server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchCassandraInstanceException.java
index ce91fa73..972ec120 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchSidecarInstanceException.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/NoSuchCassandraInstanceException.java
@@ -21,24 +21,24 @@ package org.apache.cassandra.sidecar.exceptions;
 import java.util.NoSuchElementException;
 
 /**
- * Thrown when the Sidecar instance is not found in the metadata
+ * Thrown when the Cassandra instance is not found in the metadata
  */
-public class NoSuchSidecarInstanceException extends NoSuchElementException
+public class NoSuchCassandraInstanceException extends NoSuchElementException
 {
     /**
-     * Constructs a {@link NoSuchSidecarInstanceException}, saving a reference
+     * Constructs a {@link NoSuchCassandraInstanceException}, saving a 
reference
      * to the error message string {@code errorMessage} for later retrieval by 
the
      * {@code getMessage} method.
      *
      * @param errorMessage the detail message.
      */
-    public NoSuchSidecarInstanceException(String errorMessage)
+    public NoSuchCassandraInstanceException(String errorMessage)
     {
         super(errorMessage);
     }
 
     /**
-     * Constructs a {@link NoSuchSidecarInstanceException} with the specified 
detail
+     * Constructs a {@link NoSuchCassandraInstanceException} with the 
specified detail
      * message and cause.
      *
      * @param message the detail message (which is saved for later retrieval
@@ -48,7 +48,7 @@ public class NoSuchSidecarInstanceException extends 
NoSuchElementException
      *                permitted, and indicates that the cause is nonexistent or
      *                unknown.)
      */
-    public NoSuchSidecarInstanceException(String message, Throwable cause)
+    public NoSuchCassandraInstanceException(String message, Throwable cause)
     {
         super(message);
         initCause(cause);
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/metrics/SidecarMetricsImpl.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/metrics/SidecarMetricsImpl.java
index 501ce62c..0e1d278c 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/metrics/SidecarMetricsImpl.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/metrics/SidecarMetricsImpl.java
@@ -20,7 +20,7 @@ package org.apache.cassandra.sidecar.metrics;
 
 import com.codahale.metrics.MetricRegistry;
 import org.apache.cassandra.sidecar.exceptions.CassandraUnavailableException;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.apache.cassandra.sidecar.metrics.instance.InstanceMetrics;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
@@ -55,7 +55,7 @@ public class SidecarMetricsImpl implements SidecarMetrics
     }
 
     @Override
-    public InstanceMetrics instance(String host) throws 
NoSuchSidecarInstanceException, CassandraUnavailableException
+    public InstanceMetrics instance(String host) throws 
NoSuchCassandraInstanceException, CassandraUnavailableException
     {
         return instanceMetadataFetcher.instance(host).metrics();
     }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java 
b/server/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
index 976256a0..e36bbe6d 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
@@ -36,7 +36,7 @@ import org.apache.cassandra.sidecar.common.server.data.Name;
 import org.apache.cassandra.sidecar.common.server.data.QualifiedTableName;
 import 
org.apache.cassandra.sidecar.common.server.exceptions.JmxAuthenticationException;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
@@ -209,7 +209,7 @@ public abstract class AbstractHandler<T> implements 
Handler<RoutingContext>
             return wrapHttpException(HttpResponseStatus.SERVICE_UNAVAILABLE, 
cause.getMessage(), cause);
         }
 
-        if (cause instanceof NoSuchSidecarInstanceException)
+        if (cause instanceof NoSuchCassandraInstanceException)
         {
             return wrapHttpException(HttpResponseStatus.MISDIRECTED_REQUEST, 
cause.getMessage(), cause);
         }
diff --git 
a/server/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
 
b/server/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
index c12b1710..9c6242b9 100644
--- 
a/server/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
+++ 
b/server/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
@@ -27,7 +27,7 @@ import 
org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.exceptions.CassandraUnavailableException;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -49,12 +49,12 @@ public class InstanceMetadataFetcher
      * Returns the {@link InstanceMetadata} for the given {@code host}. When 
the {@code host} is {@code null},
      * returns the first instance from the list of configured instances.
      *
-     * @param host the Cassandra instance host
+     * @param host the Cassandra instance hostname or IP address
      * @return the {@link InstanceMetadata} for the given {@code host}, or the 
first instance when {@code host} is
      * {@code null}
      */
     @NotNull
-    public InstanceMetadata instance(@Nullable String host) throws 
NoSuchSidecarInstanceException
+    public InstanceMetadata instance(@Nullable String host) throws 
NoSuchCassandraInstanceException
     {
         return host == null
                ? firstInstance()
@@ -68,10 +68,10 @@ public class InstanceMetadataFetcher
      * @param instanceId the identifier for the Cassandra instance
      * @return the {@link InstanceMetadata} for the given {@code instanceId}, 
or the first instance when
      * {@code instanceId} is {@code null}
-     * @throws NoSuchSidecarInstanceException when the Cassandra instance with 
{@code instanceId} does not exist
+     * @throws NoSuchCassandraInstanceException when the Cassandra instance 
with {@code instanceId} does not exist
      */
     @NotNull
-    public InstanceMetadata instance(int instanceId) throws 
NoSuchSidecarInstanceException
+    public InstanceMetadata instance(int instanceId) throws 
NoSuchCassandraInstanceException
     {
         return instancesMetadata.instanceFromId(instanceId);
     }
@@ -80,14 +80,14 @@ public class InstanceMetadataFetcher
      * Returns the {@link CassandraAdapterDelegate} for the given {@code 
host}. When the {@code host} is {@code null},
      * returns the delegate for the first instance from the list of configured 
instances.
      *
-     * @param host the Cassandra instance host
+     * @param host the Cassandra instance hostname or IP address
      * @return the {@link CassandraAdapterDelegate} for the given {@code 
host}, or the first instance when {@code host}
      * is {@code null}
-     * @throws NoSuchSidecarInstanceException when the Cassandra instance with 
{@code host} does not exist
+     * @throws NoSuchCassandraInstanceException when the Cassandra instance 
with {@code host} does not exist
      * @throws CassandraUnavailableException  when Cassandra is not yet 
connected
      */
     @NotNull
-    public CassandraAdapterDelegate delegate(@Nullable String host) throws 
NoSuchSidecarInstanceException, CassandraUnavailableException
+    public CassandraAdapterDelegate delegate(@Nullable String host) throws 
NoSuchCassandraInstanceException, CassandraUnavailableException
     {
         return instance(host).delegate();
     }
@@ -97,11 +97,11 @@ public class InstanceMetadataFetcher
      *
      * @param instanceId the identifier for the Cassandra instance
      * @return the {@link CassandraAdapterDelegate} for the given {@code 
instanceId}
-     * @throws NoSuchSidecarInstanceException when the Cassandra instance with 
{@code instanceId} does not exist
+     * @throws NoSuchCassandraInstanceException when the Cassandra instance 
with {@code instanceId} does not exist
      * @throws CassandraUnavailableException  when Cassandra is not yet 
connected
      */
     @NotNull
-    public CassandraAdapterDelegate delegate(int instanceId) throws 
NoSuchSidecarInstanceException, CassandraUnavailableException
+    public CassandraAdapterDelegate delegate(int instanceId) throws 
NoSuchCassandraInstanceException, CassandraUnavailableException
     {
         return instance(instanceId).delegate();
     }
diff --git 
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
 
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
index dc341770..df165815 100644
--- 
a/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
+++ 
b/server/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
@@ -52,7 +52,7 @@ import 
org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
 import org.apache.cassandra.sidecar.config.yaml.SslConfigurationImpl;
 import org.apache.cassandra.sidecar.config.yaml.TestServiceConfiguration;
 import org.apache.cassandra.sidecar.coordination.ClusterLease;
-import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
 import org.jetbrains.annotations.NotNull;
 
 import static 
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SERVER_STOP;
@@ -213,10 +213,10 @@ public class IntegrationTestModule extends AbstractModule
          *
          * @param id instance's id
          * @return instance meta information
-         * @throws NoSuchSidecarInstanceException when the instance with 
{@code id} does not exist
+         * @throws NoSuchCassandraInstanceException when the instance with 
{@code id} does not exist
          */
         @Override
-        public InstanceMetadata instanceFromId(int id) throws 
NoSuchSidecarInstanceException
+        public InstanceMetadata instanceFromId(int id) throws 
NoSuchCassandraInstanceException
         {
             return cassandraTestContext.instancesMetadata().instanceFromId(id);
         }
@@ -226,10 +226,10 @@ public class IntegrationTestModule extends AbstractModule
          *
          * @param host host address of instance
          * @return instance meta information
-         * @throws NoSuchSidecarInstanceException when the instance for {@code 
host} does not exist
+         * @throws NoSuchCassandraInstanceException when the instance for 
{@code host} does not exist
          */
         @Override
-        public InstanceMetadata instanceFromHost(String host) throws 
NoSuchSidecarInstanceException
+        public InstanceMetadata instanceFromHost(String host) throws 
NoSuchCassandraInstanceException
         {
             return 
cassandraTestContext.instancesMetadata().instanceFromHost(host);
         }
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImplTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImplTest.java
new file mode 100644
index 00000000..16832286
--- /dev/null
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/InstancesMetadataImplTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.cluster;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
+import org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
+import 
org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
+
+import static org.apache.cassandra.testing.utils.AssertionUtils.loopAssert;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.fail;
+
+class InstancesMetadataImplTest
+{
+    static final MetricRegistry METRIC_REGISTRY = new MetricRegistry();
+
+    final AtomicReference<String> localhost1NewIp = new 
AtomicReference<>(null);
+    final DnsResolver localhostResolver = new DnsResolver()
+    {
+        @Override
+        public String resolve(String hostname)
+        {
+            String ipPrefix = "127.0.0.";
+            if (hostname.startsWith("127."))
+            {
+                // it is ip address already
+                return hostname;
+            }
+
+            String trimmed = hostname.replace("localhost", "");
+            if (trimmed.isEmpty())
+            {
+                return ipPrefix + '1';
+            }
+            int digit = Integer.parseInt(trimmed);
+            if (digit == 1 && localhost1NewIp.get() != null)
+            {
+                return localhost1NewIp.get();
+            }
+            return ipPrefix + digit;
+        }
+
+        @Override
+        public String reverseResolve(String address)
+        {
+            return ""; // not examined in this test
+        }
+    };
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void testLookupByHostName()
+    {
+        List<InstanceMetadata> instances = Arrays.asList(instance(1, 
"127.0.0.1"),
+                                                         instance(2, 
"127.0.0.2"),
+                                                         instance(3, 
"127.0.0.3"));
+        InstancesMetadataImpl instancesMetadata = new 
InstancesMetadataImpl(instances, localhostResolver);
+        
assertThat(instancesMetadata.instanceFromHost("localhost").id()).isEqualTo(1);
+        
assertThat(instancesMetadata.instanceFromHost("localhost1").id()).isEqualTo(1);
+        
assertThat(instancesMetadata.instanceFromHost("localhost2").id()).isEqualTo(2);
+        
assertThat(instancesMetadata.instanceFromHost("localhost3").id()).isEqualTo(3);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.1").id()).isEqualTo(1);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.2").id()).isEqualTo(2);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.3").id()).isEqualTo(3);
+
+        assertThatThrownBy(() -> 
instancesMetadata.instanceFromHost("localhost999"))
+        .isExactlyInstanceOf(NoSuchCassandraInstanceException.class)
+        .hasMessage("Instance with host address 'localhost999' not found");
+    }
+
+    @Test
+    void testLookupByIPAddress()
+    {
+        List<InstanceMetadata> instances = Arrays.asList(instance(1, 
"localhost1"),
+                                                         instance(2, 
"localhost2"),
+                                                         instance(3, 
"localhost3"));
+        InstancesMetadataImpl instancesMetadata = new 
InstancesMetadataImpl(instances, localhostResolver);
+        
assertThat(instancesMetadata.instanceFromHost("localhost1").id()).isEqualTo(1);
+        
assertThat(instancesMetadata.instanceFromHost("localhost2").id()).isEqualTo(2);
+        
assertThat(instancesMetadata.instanceFromHost("localhost3").id()).isEqualTo(3);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.1").id()).isEqualTo(1);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.2").id()).isEqualTo(2);
+        
assertThat(instancesMetadata.instanceFromHost("127.0.0.3").id()).isEqualTo(3);
+
+        String newIp = "127.1.2.3";
+        assertThatThrownBy(() -> instancesMetadata.instanceFromHost(newIp))
+        .isExactlyInstanceOf(NoSuchCassandraInstanceException.class)
+        .hasMessage("Instance with host address '127.1.2.3' not found");
+
+        localhost1NewIp.set(newIp);
+        loopAssert(2, 100, () -> {
+            // wait for the cache to be updated
+            try
+            {
+                
assertThat(instancesMetadata.instanceFromHost(newIp).id()).isEqualTo(1);
+            }
+            catch (NoSuchCassandraInstanceException e)
+            {
+                fail(e.getMessage());
+                // continue
+            }
+        });
+    }
+
+    @Test
+    void testLookupById()
+    {
+        List<InstanceMetadata> instances = Arrays.asList(instance(1, 
"localhost1"),
+                                                         instance(2, 
"localhost2"),
+                                                         instance(3, 
"localhost3"));
+        InstancesMetadataImpl instancesMetadata = new 
InstancesMetadataImpl(instances, localhostResolver);
+        
assertThat(instancesMetadata.instanceFromId(1).host()).isEqualTo("localhost1");
+        
assertThat(instancesMetadata.instanceFromId(2).host()).isEqualTo("localhost2");
+        
assertThat(instancesMetadata.instanceFromId(3).host()).isEqualTo("localhost3");
+
+        assertThatThrownBy(() -> instancesMetadata.instanceFromId(123))
+        .isExactlyInstanceOf(NoSuchCassandraInstanceException.class)
+        .hasMessage("Instance id '123' not found");
+    }
+
+    InstanceMetadata instance(int id, String hostNameOrIp)
+    {
+        String root = tempDir.toString();
+        return InstanceMetadataImpl.builder()
+                                   .id(id)
+                                   .host(hostNameOrIp, localhostResolver)
+                                   .port(9042)
+                                   .storageDir(root)
+                                   .metricRegistry(METRIC_REGISTRY)
+                                   .build();
+    }
+}
diff --git 
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImplTest.java
 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImplTest.java
index fa4a024f..07029a9e 100644
--- 
a/server/src/test/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImplTest.java
+++ 
b/server/src/test/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImplTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.cassandra.sidecar.cluster.instance;
 
+import java.net.UnknownHostException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -28,15 +29,17 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import com.codahale.metrics.MetricRegistry;
+import org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static 
org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 class InstanceMetadataImplTest
 {
     private static final int ID = 123;
-    private static final String HOST = "testhost";
+    private static final String HOST = "localhost";
     private static final int PORT = 12345;
     private static final String DATA_DIR_1 = "test/data/data1";
     private static final String DATA_DIR_2 = "test/data/data2";
@@ -270,7 +273,33 @@ class InstanceMetadataImplTest
         assertThat(metadata.savedCachesDir()).isEqualTo(rootDir + 
"/saved_caches");
     }
 
-    InstanceMetadataImpl.Builder getInstanceMetadataBuilder(String rootDir)
+    @Test
+    void testResolveIpAddress() throws Exception
+    {
+        String rootDir = tempDir.toString();
+        InstanceMetadataImpl instance = 
getInstanceMetadataBuilder(rootDir).host("localhost").build();
+        instance.refreshIpAddress();
+        assertThat(instance.ipAddress()).isEqualTo("127.0.0.1");
+
+        String host = "cassandra.sidecar.org";
+        instance = getInstanceMetadataBuilder(rootDir).host(host, 
createDnsResolver(host, "127.0.0.1")).build();
+        instance.refreshIpAddress();
+        assertThat(instance.ipAddress()).isEqualTo("127.0.0.1");
+    }
+
+    @Test
+    void testIpAddressResolutionFails()
+    {
+        String rootDir = tempDir.toString();
+        InstanceMetadataImpl instanceMetadata = 
getInstanceMetadataBuilder(rootDir)
+                                                .host("my_host", 
createDnsResolver("localhost", "127.0.0.1"))
+                                                .build();
+        assertThatThrownBy(instanceMetadata::refreshIpAddress)
+        .isExactlyInstanceOf(UnknownHostException.class)
+        .hasMessage("my_host");
+    }
+
+    static InstanceMetadataImpl.Builder getInstanceMetadataBuilder(String 
rootDir)
     {
         List<String> dataDirs = new ArrayList<>();
         dataDirs.add(rootDir + "/" + DATA_DIR_1);
@@ -289,4 +318,26 @@ class InstanceMetadataImplTest
                                    .localSystemDataFileDir(rootDir + "/" + 
LOCAL_SYSTEM_DATA_FILE_DIR)
                                    .metricRegistry(METRIC_REGISTRY);
     }
+
+    private DnsResolver createDnsResolver(String hostName, String ipAddress)
+    {
+        return new DnsResolver()
+        {
+            @Override
+            public String resolve(String toResolve) throws UnknownHostException
+            {
+                if (toResolve.equalsIgnoreCase(hostName))
+                {
+                    return ipAddress;
+                }
+                throw new UnknownHostException(toResolve);
+            }
+
+            @Override
+            public String reverseResolve(String address) throws 
UnknownHostException
+            {
+                return ""; // won't be examined
+            }
+        };
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to