Skip to content
Permalink
Browse files
Avoid having sidecar's health response code depend on Cassandra's hea…
…lth information

patch by Saranya Krishnakumar; reviewed by Yifan Cai, Dinesh Joshi for CASSANDRASC-29
  • Loading branch information
sarankk authored and yifan-c committed Sep 29, 2021
1 parent 4e4b8ea commit 4d88af6eacd8aaf50fb04d6889c8f8c395a4ff45
Showing 6 changed files with 103 additions and 38 deletions.
@@ -45,6 +45,7 @@
import org.apache.cassandra.sidecar.common.CQLSession;
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
import org.apache.cassandra.sidecar.routes.CassandraHealthService;
import org.apache.cassandra.sidecar.routes.HealthService;
import org.apache.cassandra.sidecar.routes.StreamSSTableComponent;
import org.apache.cassandra.sidecar.routes.SwaggerOpenApiResource;
@@ -102,7 +103,8 @@ public HttpServer vertxServer(Vertx vertx, Configuration conf, Router router, Ve
@Singleton
private VertxRequestHandler configureServices(Vertx vertx,
HealthService healthService,
StreamSSTableComponent ssTableComponent)
StreamSSTableComponent ssTableComponent,
CassandraHealthService cassandraHealthService)
{
VertxResteasyDeployment deployment = new VertxResteasyDeployment();
deployment.start();
@@ -111,6 +113,7 @@ private VertxRequestHandler configureServices(Vertx vertx,
r.addPerInstanceResource(SwaggerOpenApiResource.class);
r.addSingletonResource(healthService);
r.addSingletonResource(ssTableComponent);
r.addSingletonResource(cassandraHealthService);

return new VertxRequestHandler(vertx, deployment);
}
@@ -0,0 +1,70 @@
/*
* 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.Inject;
import com.google.inject.Singleton;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.vertx.core.json.Json;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;

/**
* Provides a simple REST endpoint to determine if a node is available
*/
@Singleton
@Path("/api/v1/cassandra/__health")
public class CassandraHealthService
{
private static final Logger logger = LoggerFactory.getLogger(HealthService.class);
private final CassandraAdapterDelegate cassandra;

@Inject
public CassandraHealthService(CassandraAdapterDelegate cassandra)
{
this.cassandra = cassandra;
}

@Operation(summary = "Health Check for Cassandra's status",
description = "Returns HTTP 200 if Cassandra is available, 503 otherwise",
responses = {
@ApiResponse(responseCode = "200", description = "Cassandra is available"),
@ApiResponse(responseCode = "503", description = "Cassandra is not available")
})
@Produces(MediaType.APPLICATION_JSON)
@GET
public Response getCassandraHealth()
{
Boolean up = cassandra.isUp();
int status = up ? HttpResponseStatus.OK.code() : HttpResponseStatus.SERVICE_UNAVAILABLE.code();
return Response.status(status).entity(Json.encode(ImmutableMap.of("status", up ?
"OK" : "NOT_OK"))).build();
}
}
@@ -26,45 +26,29 @@

import com.google.common.collect.ImmutableMap;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.vertx.core.json.Json;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;

/**
* Provides a simple REST endpoint to determine if a node is available
* Provides a simple REST endpoint to determine if Sidecar is available
*/
@Singleton
@Path("/api/v1/__health")
public class HealthService
{
private static final Logger logger = LoggerFactory.getLogger(HealthService.class);
private final CassandraAdapterDelegate cassandra;

@Inject
public HealthService(CassandraAdapterDelegate cassandra)
{
this.cassandra = cassandra;
}

@Operation(summary = "Health Check for Cassandra's status",
description = "Returns HTTP 200 if Cassandra is available, 503 otherwise",
responses = {
@ApiResponse(responseCode = "200", description = "Cassandra is available"),
@ApiResponse(responseCode = "503", description = "Cassandra is not available")
})
@Operation(summary = "Health Check for Sidecar's status",
description = "Returns HTTP 200 if Sidecar is available")
@Produces(MediaType.APPLICATION_JSON)
@GET
public Response doGet()
public Response getSidecarHealth()
{
Boolean up = cassandra.isUp();
int status = up ? HttpResponseStatus.OK.code() : HttpResponseStatus.SERVICE_UNAVAILABLE.code();
return Response.status(status).entity(Json.encode(ImmutableMap.of("status", up ?
"OK" : "NOT_OK"))).build();
return Response.status(HttpResponseStatus.OK.code()).entity(Json.encode(ImmutableMap.of("status", "OK")))
.build();
}
}
@@ -96,11 +96,10 @@ void tearDown() throws InterruptedException
logger.error("Close event timed out.");
}

@DisplayName("Should return HTTP 200 OK when check=True")
@DisplayName("Should return HTTP 200 OK if sidecar server is running")
@Test
public void testHealthCheckReturns200OK(VertxTestContext testContext)
public void testSidecarHealthCheckReturnsOK(VertxTestContext testContext)
{
when(cassandra.isUp()).thenReturn(true);
WebClient client = getClient();

client.get(config.getPort(), "localhost", "/api/v1/__health")
@@ -109,6 +108,7 @@ public void testHealthCheckReturns200OK(VertxTestContext testContext)
.send(testContext.succeeding(response -> testContext.verify(() ->
{
Assert.assertEquals(200, response.statusCode());
Assert.assertEquals("{\"status\":\"OK\"}", response.body());
testContext.completeNow();
})));
}
@@ -129,20 +129,38 @@ private WebClientOptions getWebClientOptions()
return options;
}

@DisplayName("Should return HTTP 200 OK when check=True")
@Test
public void testHealthCheckReturns200OK(VertxTestContext testContext)
{
when(cassandra.isUp()).thenReturn(true);
WebClient client = getClient();

client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health")
.as(BodyCodec.string())
.ssl(isSslEnabled())
.send(testContext.succeeding(response -> testContext.verify(() ->
{
Assert.assertEquals(200, response.statusCode());
Assert.assertEquals("{\"status\":\"OK\"}", response.body());
testContext.completeNow();
})));
}

@DisplayName("Should return HTTP 503 Failure when check=False")
@Test
public void testHealthCheckReturns503Failure(VertxTestContext testContext)
{

when(cassandra.isUp()).thenReturn(false);
WebClient client = getClient();

client.get(config.getPort(), "localhost", "/api/v1/__health")
client.get(config.getPort(), "localhost", "/api/v1/cassandra/__health")
.as(BodyCodec.string())
.ssl(isSslEnabled())
.send(testContext.succeeding(response -> testContext.verify(() ->
{
Assert.assertEquals(503, response.statusCode());
Assert.assertEquals("{\"status\":\"NOT_OK\"}", response.body());
testContext.completeNow();
})));
}
@@ -18,7 +18,6 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* FilePathBuilderTest
@@ -29,7 +29,6 @@
import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
import org.apache.cassandra.sidecar.common.MockCassandraFactory;
import org.apache.cassandra.sidecar.routes.HealthService;

import static org.mockito.Mockito.mock;

@@ -47,14 +46,6 @@ public CassandraAdapterDelegate delegate()
return mock(CassandraAdapterDelegate.class);
}

@Singleton
@Provides
public HealthService healthService(CassandraAdapterDelegate delegate)
{
return new HealthService(delegate);
}


@Provides
@Singleton
public Configuration configuration()

0 comments on commit 4d88af6

Please sign in to comment.