CASSANDRASC-50: Deprecate the sidecar cassandra health endpoint containing instance segment

This commit deprecates the Cassandra Health endpoint containing the instance segment
in the path. This endpoint is currently unused and it is replaced by the health
endpoint with the `instanceId` query string parameter. Since the `instanceId` is optional
we move the path param (mandatory) to the query param (optional).

This commit also moves the CassandraHealthService from jax RS to a vertx Handler.
It also moves the HealthService to a inlined handler, simplifying the service.

patch by Francisco Guerrero; reviewed by Yifan Cai, Dinesh Joshi for CASSANDRASC-50
diff --git a/CHANGES.txt b/CHANGES.txt
index aec5dc7..73870a7 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * 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)
  * Delegate methods to the RateLimiter (CASSANDRASC-45)
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java b/common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
index e159433..f68c469 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/ApiEndpointsV1.java
@@ -26,6 +26,7 @@
     public static final String API = "/api";
     public static final String API_V1 = API + "/v1";
 
+    public static final String HEALTH = "/__health";
     public static final String CASSANDRA = "/cassandra";
     public static final String KEYSPACE_PATH_PARAM = ":keyspace";
     public static final String TABLE_PATH_PARAM = ":table";
@@ -39,6 +40,9 @@
     public static final String PER_COMPONENT = "/components/" + COMPONENT_PATH_PARAM;
     public static final String PER_UPLOAD = "/uploads/" + UPLOAD_ID_PATH_PARAM;
 
+    public static final String HEALTH_ROUTE = API_V1 + HEALTH;
+    public static final String CASSANDRA_HEALTH_ROUTE = API_V1 + CASSANDRA + HEALTH;
+
     @Deprecated  // NOTE: Uses singular forms of "keyspace" and "table"
     public static final String DEPRECATED_SNAPSHOTS_ROUTE = API_V1 + "/keyspace/" + KEYSPACE_PATH_PARAM +
                                                             "/table/" + TABLE_PATH_PARAM +
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index 581692b..fa0e196 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -19,6 +19,8 @@
 package org.apache.cassandra.sidecar;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import com.google.common.util.concurrent.SidecarRateLimiter;
@@ -46,10 +48,9 @@
 import org.apache.cassandra.sidecar.common.dns.DnsResolver;
 import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 import org.apache.cassandra.sidecar.logging.SidecarLoggerHandler;
-import org.apache.cassandra.sidecar.routes.CassandraHealthService;
+import org.apache.cassandra.sidecar.routes.CassandraHealthHandler;
 import org.apache.cassandra.sidecar.routes.FileStreamHandler;
 import org.apache.cassandra.sidecar.routes.GossipInfoHandler;
-import org.apache.cassandra.sidecar.routes.HealthService;
 import org.apache.cassandra.sidecar.routes.JsonErrorHandler;
 import org.apache.cassandra.sidecar.routes.RingHandler;
 import org.apache.cassandra.sidecar.routes.SchemaHandler;
@@ -73,6 +74,9 @@
  */
 public class MainModule extends AbstractModule
 {
+    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");
+
     @Provides
     @Singleton
     public Vertx vertx()
@@ -114,17 +118,12 @@
 
     @Provides
     @Singleton
-    private VertxRequestHandler configureServices(Vertx vertx,
-                                                  HealthService healthService,
-                                                  CassandraHealthService cassandraHealthService)
+    private VertxRequestHandler configureServices(Vertx vertx)
     {
         VertxResteasyDeployment deployment = new VertxResteasyDeployment();
         deployment.start();
         VertxRegistry registry = deployment.getRegistry();
-
         registry.addPerInstanceResource(SwaggerOpenApiResource.class);
-        registry.addSingletonResource(healthService);
-        registry.addSingletonResource(cassandraHealthService);
 
         return new VertxRequestHandler(vertx, deployment);
     }
@@ -133,6 +132,7 @@
     @Singleton
     public Router vertxRouter(Vertx vertx,
                               Configuration conf,
+                              CassandraHealthHandler cassandraHealthHandler,
                               StreamSSTableComponentHandler streamSSTableComponentHandler,
                               FileStreamHandler fileStreamHandler,
                               SnapshotsHandler snapshotsHandler,
@@ -170,6 +170,13 @@
               .handler(docs);
 
         // Add custom routers
+        // Provides a simple REST endpoint to determine if Sidecar is available
+        router.get(ApiEndpointsV1.HEALTH_ROUTE)
+              .handler(context -> context.json(OK_STATUS));
+
+        router.get(ApiEndpointsV1.CASSANDRA_HEALTH_ROUTE)
+              .handler(cassandraHealthHandler);
+
         //noinspection deprecation
         router.get(ApiEndpointsV1.DEPRECATED_COMPONENTS_ROUTE)
               .handler(streamSSTableComponentHandler)
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java
index a15d706..cf19a06 100644
--- a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfig.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.sidecar.cluster;
 
 import java.util.List;
+import java.util.NoSuchElementException;
 
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 
@@ -37,14 +38,16 @@
      *
      * @param id instance's id
      * @return instance meta information
+     * @throws NoSuchElementException when the instance with {@code id} does not exist
      */
-    InstanceMetadata instanceFromId(final int id);
+    InstanceMetadata instanceFromId(int id) throws NoSuchElementException;
 
     /**
      * Lookup instance metadata by host name.
      *
      * @param host host address of instance
      * @return instance meta information
+     * @throws NoSuchElementException when the instance for {@code host} does not exist
      */
-    InstanceMetadata instanceFromHost(final String host);
+    InstanceMetadata instanceFromHost(String host) throws NoSuchElementException;
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
index ba5a488..0fcda22 100644
--- a/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
+++ b/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
@@ -21,6 +21,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -51,27 +52,30 @@
         this.instanceMetadataList = instanceMetadataList;
     }
 
+    @Override
     public List<InstanceMetadata> instances()
     {
         return instanceMetadataList;
     }
 
-    public InstanceMetadata instanceFromId(int id)
+    @Override
+    public InstanceMetadata instanceFromId(int id) throws NoSuchElementException
     {
         InstanceMetadata instanceMetadata = idToInstanceMetadata.get(id);
         if (instanceMetadata == null)
         {
-            throw new IllegalArgumentException("Instance id " + id + " not found");
+            throw new NoSuchElementException("Instance id " + id + " not found");
         }
         return instanceMetadata;
     }
 
-    public InstanceMetadata instanceFromHost(String host)
+    @Override
+    public InstanceMetadata instanceFromHost(String host) throws NoSuchElementException
     {
         InstanceMetadata instanceMetadata = hostToInstanceMetadata.get(host);
         if (instanceMetadata == null)
         {
-            throw new IllegalArgumentException("Instance with host address " + host + " not found");
+            throw new NoSuchElementException("Instance with host address " + host + " not found");
         }
         return instanceMetadata;
     }
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
index e46c812..78c8b24 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
@@ -18,6 +18,8 @@
 
 package org.apache.cassandra.sidecar.routes;
 
+import java.util.NoSuchElementException;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -138,7 +140,7 @@
                 throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
                                         "InstanceId query parameter must be a valid integer");
             }
-            catch (IllegalArgumentException | IllegalStateException ex)
+            catch (NoSuchElementException | IllegalStateException ex)
             {
                 throw new HttpException(HttpResponseStatus.NOT_FOUND.code(), ex.getMessage());
             }
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java
new file mode 100644
index 0000000..8530015
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java
@@ -0,0 +1,93 @@
+/*
+ * 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.routes;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.json.Json;
+import io.vertx.core.net.SocketAddress;
+import io.vertx.ext.web.RoutingContext;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
+import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
+import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
+
+import static org.apache.cassandra.sidecar.MainModule.NOT_OK_STATUS;
+import static org.apache.cassandra.sidecar.MainModule.OK_STATUS;
+
+/**
+ * Provides a simple REST endpoint to determine if a Cassandra node is available
+ */
+@Singleton
+public class CassandraHealthHandler extends AbstractHandler<Void>
+{
+    /**
+     * Constructs a handler with the provided {@code metadataFetcher}
+     *
+     * @param metadataFetcher the interface to retrieve instance metadata
+     * @param executorPools   the executor pools for blocking executions
+     * @param validator       a validator instance to validate Cassandra-specific input
+     */
+    @Inject
+    protected CassandraHealthHandler(InstanceMetadataFetcher metadataFetcher,
+                                     ExecutorPools executorPools,
+                                     CassandraInputValidator validator)
+    {
+        super(metadataFetcher, executorPools, validator);
+    }
+
+    /**
+     * Handles the request with the parameters for this request.
+     *
+     * @param context       the request context
+     * @param httpRequest   the {@link HttpServerRequest} object
+     * @param host          the host where this request is intended for
+     * @param remoteAddress the address where the request originates
+     * @param request       the request object
+     */
+    @Override
+    protected void handleInternal(RoutingContext context,
+                                  HttpServerRequest httpRequest,
+                                  String host,
+                                  SocketAddress remoteAddress,
+                                  Void request)
+    {
+        CassandraAdapterDelegate delegate = metadataFetcher.delegate(host);
+        if (delegate != null && delegate.isUp())
+        {
+            context.json(OK_STATUS);
+        }
+        else
+        {
+            context.response()
+                   .setStatusCode(HttpResponseStatus.SERVICE_UNAVAILABLE.code())
+                   .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json")
+                   .end(Json.CODEC.toString(NOT_OK_STATUS, false));
+        }
+    }
+
+    @Override
+    protected Void extractParamsOrThrow(RoutingContext context)
+    {
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java b/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java
deleted file mode 100644
index 4620693..0000000
--- a/src/main/java/org/apache/cassandra/sidecar/routes/HealthService.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.routes;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import com.google.common.collect.ImmutableMap;
-
-import com.google.inject.Singleton;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.swagger.v3.oas.annotations.Operation;
-import io.vertx.core.json.Json;
-
-/**
- * Provides a simple REST endpoint to determine if Sidecar is available
- */
-@Singleton
-@Path("/api/v1/__health")
-public class HealthService
-{
-    @Operation(summary = "Health Check for Sidecar's status",
-    description = "Returns HTTP 200 if Sidecar is available")
-    @Produces(MediaType.APPLICATION_JSON)
-    @GET
-    public Response sidecarHealth()
-    {
-        return Response.status(HttpResponseStatus.OK.code()).entity(Json.encode(ImmutableMap.of("status", "OK")))
-                       .build();
-    }
-}
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/SwaggerOpenApiResource.java b/src/main/java/org/apache/cassandra/sidecar/routes/SwaggerOpenApiResource.java
index 430b69f..71b4d13 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/SwaggerOpenApiResource.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/SwaggerOpenApiResource.java
@@ -19,7 +19,6 @@
 package org.apache.cassandra.sidecar.routes;
 
 import java.util.Collections;
-import java.util.HashSet;
 import javax.servlet.ServletConfig;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -71,6 +70,6 @@
     static
     {
         Reader reader = new Reader(new SwaggerConfiguration());
-        OAS = reader.read(new HashSet<>(Collections.singletonList(HealthService.class)));
+        OAS = reader.read(Collections.emptySet());
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java b/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
index 1d7d69c..f8661e5 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/InstanceMetadataFetcher.java
@@ -25,8 +25,6 @@
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.jetbrains.annotations.Nullable;
 
-import static org.apache.cassandra.sidecar.routes.AbstractHandler.extractHostAddressWithoutPort;
-
 /**
  * Helper class to retrieve instance information from an instanceId or hostname.
  */
@@ -53,7 +51,7 @@
     {
         return host == null
                ? firstInstance()
-               : instancesConfig.instanceFromHost(extractHostAddressWithoutPort(host));
+               : instancesConfig.instanceFromHost(host);
     }
 
     /**
diff --git a/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java b/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
index 6a3bc77..ac36c3b 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
@@ -80,8 +80,7 @@
                                          TEST_KEYSPACE, table);
         client.put(config.getPort(), "localhost", testRoute)
               .expect(ResponsePredicate.SC_OK)
-              .send(context.succeeding(response -> context.verify(() ->
-              {
+              .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
 
                   // creating the snapshot with the same name will return a 409 (Conflict) status code
@@ -105,8 +104,7 @@
                                          TEST_KEYSPACE, table);
         client.put(config.getPort(), "localhost", testRoute)
               .expect(ResponsePredicate.SC_OK)
-              .send(context.succeeding(response -> context.verify(() ->
-              {
+              .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
 
                   // validate that the snapshot is created
@@ -175,29 +173,25 @@
 
         // first create the snapshot
         client.put(config.getPort(), "localhost", testRoute)
-              .expect(ResponsePredicate.SC_OK)
-              .send(context.succeeding(createResponse -> context.verify(() ->
-              {
+              .send(context.succeeding(createResponse -> context.verify(() -> {
                   assertThat(createResponse.statusCode()).isEqualTo(OK.code());
                   ExtendedCassandraContainer container = cassandraTestContext.container;
-                  final String directory = container.execInContainer("find",
-                                                                     "/opt/cassandra/data/",
-                                                                     "-name",
-                                                                     "my-snapshot").getStdout().trim();
+                  String directory = container.execInContainer("find",
+                                                               "/opt/cassandra/data/",
+                                                               "-name",
+                                                               "my-snapshot").getStdout().trim();
                   // snapshot directory exists inside container
                   assertThat(directory).isNotBlank();
 
                   // then delete the snapshot
                   client.delete(config.getPort(), "localhost", testRoute)
-                        .expect(ResponsePredicate.SC_OK)
-                        .send(context.succeeding(deleteResponse -> context.verify(() ->
-                        {
+                        .send(context.succeeding(deleteResponse -> context.verify(() -> {
                             assertThat(deleteResponse.statusCode()).isEqualTo(OK.code());
                             // validate that the snapshot is removed
-                            final String removedDir = container.execInContainer("find",
-                                                                                "/opt/cassandra/data/",
-                                                                                "-name",
-                                                                                "my-snapshot").getStdout().trim();
+                            String removedDir = container.execInContainer("find",
+                                                                          "/opt/cassandra/data/",
+                                                                          "-name",
+                                                                          "my-snapshot").getStdout().trim();
                             assertThat(removedDir).isEmpty();
 
                             context.completeNow();
diff --git a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
index 47e47ed..ba01bc9 100644
--- a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
@@ -40,6 +40,7 @@
 import io.vertx.ext.web.codec.BodyCodec;
 import io.vertx.junit5.VertxTestContext;
 
+import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
 import static io.netty.handler.codec.http.HttpResponseStatus.OK;
 import static io.netty.handler.codec.http.HttpResponseStatus.SERVICE_UNAVAILABLE;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -51,7 +52,6 @@
 {
     private static final Logger logger = LoggerFactory.getLogger(AbstractHealthServiceTest.class);
     private Vertx vertx;
-    private Configuration config;
     private HttpServer server;
 
     public abstract boolean isSslEnabled();
@@ -70,10 +70,9 @@
         Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(testModule()));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
@@ -92,11 +91,11 @@
 
     @DisplayName("Should return HTTP 200 OK if sidecar server is running")
     @Test
-    public void testSidecarHealthCheckReturnsOK(VertxTestContext testContext)
+    void testSidecarHealthCheckReturnsOK(VertxTestContext testContext)
     {
         WebClient client = client();
 
-        client.get(config.getPort(), "localhost", "/api/v1/__health")
+        client.get(server.actualPort(), "localhost", "/api/v1/__health")
               .as(BodyCodec.string())
               .ssl(isSslEnabled())
               .send(testContext.succeeding(response -> testContext.verify(() ->
@@ -125,11 +124,11 @@
 
     @DisplayName("Should return HTTP 200 OK when cassandra instance is up")
     @Test
-    public void testHealthCheckReturns200OK(VertxTestContext testContext)
+    void testHealthCheckReturns200OK(VertxTestContext testContext)
     {
         WebClient client = client();
 
-        client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health")
+        client.get(server.actualPort(), "localhost", "/api/v1/cassandra/__health")
               .as(BodyCodec.string())
               .ssl(isSslEnabled())
               .send(testContext.succeeding(response -> testContext.verify(() ->
@@ -140,13 +139,13 @@
               })));
     }
 
-    @DisplayName("Should return HTTP 503 Failure when instance is down")
+    @DisplayName("Should return HTTP 503 Failure when instance is down with query param")
     @Test
-    public void testHealthCheckReturns503Failure(VertxTestContext testContext)
+    void testHealthCheckReturns503FailureWithQueryParam(VertxTestContext testContext)
     {
         WebClient client = client();
 
-        client.get(config.getPort(), "localhost", "/api/v1/cassandra/instance/2/__health")
+        client.get(server.actualPort(), "localhost", "/api/v1/cassandra/__health?instanceId=2")
               .as(BodyCodec.string())
               .ssl(isSslEnabled())
               .send(testContext.succeeding(response -> testContext.verify(() ->
@@ -157,19 +156,21 @@
               })));
     }
 
-    @DisplayName("Should return HTTP 503 Failure when instance is down with query param")
+    @DisplayName("Should return HTTP 404 (NOT FOUND) when instance is not found")
     @Test
-    public void testHealthCheckReturns503FailureWithQueryParam(VertxTestContext testContext)
+    void testHealthCheckReturns404NotFound(VertxTestContext testContext)
     {
         WebClient client = client();
 
-        client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health?instanceId=2")
+        // instance with ID=400 does not exist
+        client.get(server.actualPort(), "localhost", "/api/v1/cassandra/__health?instanceId=400")
               .as(BodyCodec.string())
               .ssl(isSslEnabled())
               .send(testContext.succeeding(response -> testContext.verify(() ->
               {
-                  assertThat(response.statusCode()).isEqualTo(SERVICE_UNAVAILABLE.code());
-                  assertThat(response.body()).isEqualTo("{\"status\":\"NOT_OK\"}");
+                  assertThat(response.statusCode()).isEqualTo(NOT_FOUND.code());
+                  assertThat(response.body())
+                  .isEqualTo("{\"status\":\"Not Found\",\"code\":404,\"message\":\"Instance id 400 not found\"}");
                   testContext.completeNow();
               })));
     }