Skip to content

Commit

Permalink
feat: Health endpoints (#111)
Browse files Browse the repository at this point in the history
Signed-off-by: Alfredo Gutierrez <[email protected]>
  • Loading branch information
AlfredoG87 authored Aug 16, 2024
1 parent 90dd282 commit a8bc5ec
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 15 deletions.
18 changes: 8 additions & 10 deletions charts/hedera-block-node/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,14 @@ spec:
name: {{ include "hedera-block-node.fullname" . }}-config
- secretRef:
name: {{ include "hedera-block-node.fullname" . }}-secret

# TODO: Uncomment them once we have the health and readiness probes in our application
#livenessProbe:
# httpGet:
# path: /health # we need to define this in our application
# port: 8080 # will be the same as server port?
#readinessProbe:
# httpGet:
# path: /ready # we need to define this in our application
# port: 8080 # will be the same as server port?
livenessProbe:
httpGet:
path: {{ .Values.blockNode.health.liveness.endpoint }}
port: {{ .Values.service.port }}
readinessProbe:
httpGet:
path: {{ .Values.blockNode.health.readiness.endpoint }}
port: {{ .Values.service.port }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
5 changes: 5 additions & 0 deletions charts/hedera-block-node/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,8 @@ blockNode:
secret:
# if blank will use same as AppVersion of chart.
PRIVATE_KEY: "fake_private_key"
health:
readiness:
endpoint: "/healthz/readiness"
liveness:
endpoint: "/healthz/liveness"
22 changes: 21 additions & 1 deletion server/src/main/java/com/hedera/block/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.hedera.block.server.config.BlockNodeContext;
import com.hedera.block.server.config.BlockNodeContextFactory;
import com.hedera.block.server.data.ObjectEvent;
import com.hedera.block.server.health.HealthService;
import com.hedera.block.server.health.HealthServiceImpl;
import com.hedera.block.server.mediator.LiveStreamMediatorBuilder;
import com.hedera.block.server.mediator.StreamMediator;
import com.hedera.block.server.persistence.storage.PersistenceStorageConfig;
Expand All @@ -32,6 +34,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.grpc.GrpcRouting;
import io.helidon.webserver.http.HttpRouting;
import java.io.IOException;

/** Main class for the block node server */
Expand Down Expand Up @@ -82,16 +85,33 @@ public static void main(final String[] args) {
final GrpcRouting.Builder grpcRouting =
GrpcRouting.builder().service(blockStreamService);

@NonNull final HealthService healthService = new HealthServiceImpl(serviceStatus);

@NonNull
final HttpRouting.Builder httpRouting =
HttpRouting.builder()
.register(healthService.getHealthRootPath(), healthService);

// Build the web server
// TODO: make port server a configurable value.
@NonNull
final WebServer webServer =
WebServer.builder().port(8080).addRouting(grpcRouting).build();
WebServer.builder()
.port(8080)
.addRouting(grpcRouting)
.addRouting(httpRouting)
.build();

// Update the serviceStatus with the web server
serviceStatus.setWebServer(webServer);

// Start the web server
webServer.start();

// Log the server status
LOGGER.log(
System.Logger.Level.INFO,
"Block Node Server started at port: " + webServer.port());
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed 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 com.hedera.block.server.health;

import edu.umd.cs.findbugs.annotations.NonNull;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;

/** Defines the contract for health http service, needed for implementing an standard */
public interface HealthService extends HttpService {
/**
* The path for the health group endpoints. Root path for all health endpoints.
*
* @return the root path for the health group endpoints
*/
@NonNull
String getHealthRootPath();

/**
* Handles the request for liveness endpoint, that it most be defined on routing implementation.
*
* @param req the server request
* @param res the server response
*/
void handleLiveness(@NonNull final ServerRequest req, @NonNull final ServerResponse res);

/**
* Handles the request for readiness endpoint, that it most be defined on routing
* implementation.
*
* @param req the server request
* @param res the server response
*/
void handleReadiness(@NonNull final ServerRequest req, @NonNull final ServerResponse res);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed 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 com.hedera.block.server.health;

import com.hedera.block.server.ServiceStatus;
import edu.umd.cs.findbugs.annotations.NonNull;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;

/** Provides implementation for the health endpoints of the server. */
public class HealthServiceImpl implements HealthService {

private static final String LIVENESS_PATH = "/liveness";
private static final String READINESS_PATH = "/readiness";

private final ServiceStatus serviceStatus;

/**
* It initializes the HealthService with needed dependencies.
*
* @param serviceStatus is used to check the status of the service
*/
public HealthServiceImpl(@NonNull ServiceStatus serviceStatus) {
this.serviceStatus = serviceStatus;
}

@Override
@NonNull
public String getHealthRootPath() {
return "/healthz";
}

/**
* Configures the health routes for the server.
*
* @param httpRules is used to configure the health endpoints routes
*/
@Override
public void routing(@NonNull final HttpRules httpRules) {
httpRules
.get(LIVENESS_PATH, this::handleLiveness)
.get(READINESS_PATH, this::handleReadiness);
}

/**
* Handles the request for liveness endpoint, that it most be defined on routing implementation.
*
* @param req the server request
* @param res the server response
*/
@Override
public final void handleLiveness(
@NonNull final ServerRequest req, @NonNull final ServerResponse res) {
if (serviceStatus.isRunning()) {
res.status(200).send("OK");
} else {
res.status(503).send("Service is not running");
}
}

/**
* Handles the request for readiness endpoint, that it most be defined on routing
* implementation.
*
* @param req the server request
* @param res the server response
*/
@Override
public final void handleReadiness(
@NonNull final ServerRequest req, @NonNull final ServerResponse res) {
if (serviceStatus.isRunning()) {
res.status(200).send("OK");
} else {
res.status(503).send("Service is not running");
}
}
}
6 changes: 2 additions & 4 deletions server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

/** Runtime module of the server. */
module com.hedera.block.server {
exports com.hedera.block.server.consumer to
com.swirlds.config.impl;
exports com.hedera.block.server.persistence.storage to
com.swirlds.config.impl;
exports com.hedera.block.server.consumer;
exports com.hedera.block.server.persistence.storage;

requires com.hedera.block.protos;
requires com.google.protobuf;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed 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 com.hedera.block.server.health;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

import com.hedera.block.server.ServiceStatus;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class HealthServiceTest {

private static final String READINESS_PATH = "/readiness";
private static final String LIVENESS_PATH = "/liveness";
private static final String HEALTH_PATH = "/healthz";

@Mock private ServiceStatus serviceStatus;

@Mock ServerRequest serverRequest;

@Mock ServerResponse serverResponse;

@Test
public void testHandleLiveness() {
// given
when(serviceStatus.isRunning()).thenReturn(true);
when(serverResponse.status(200)).thenReturn(serverResponse);
doNothing().when(serverResponse).send("OK");
HealthService healthService = new HealthServiceImpl(serviceStatus);

// when
healthService.handleLiveness(serverRequest, serverResponse);

// then
verify(serverResponse, times(1)).status(200);
verify(serverResponse, times(1)).send("OK");
}

@Test
public void testHandleLiveness_notRunning() {
// given
when(serviceStatus.isRunning()).thenReturn(false);
when(serverResponse.status(503)).thenReturn(serverResponse);
doNothing().when(serverResponse).send("Service is not running");
HealthService healthService = new HealthServiceImpl(serviceStatus);

// when
healthService.handleLiveness(serverRequest, serverResponse);

// then
verify(serverResponse, times(1)).status(503);
verify(serverResponse, times(1)).send("Service is not running");
}

@Test
public void testHandleReadiness() {
// given
when(serviceStatus.isRunning()).thenReturn(true);
when(serverResponse.status(200)).thenReturn(serverResponse);
doNothing().when(serverResponse).send("OK");
HealthService healthService = new HealthServiceImpl(serviceStatus);

// when
healthService.handleReadiness(serverRequest, serverResponse);

// then
verify(serverResponse, times(1)).status(200);
verify(serverResponse, times(1)).send("OK");
}

@Test
public void testHandleReadiness_notRunning() {
// given
when(serviceStatus.isRunning()).thenReturn(false);
when(serverResponse.status(503)).thenReturn(serverResponse);
doNothing().when(serverResponse).send("Service is not running");
HealthService healthService = new HealthServiceImpl(serviceStatus);

// when
healthService.handleReadiness(serverRequest, serverResponse);

// then
verify(serverResponse, times(1)).status(503);
verify(serverResponse, times(1)).send("Service is not running");
}

@Test
public void testRouting() {
// given
HealthService healthService = new HealthServiceImpl(serviceStatus);
HttpRules httpRules = mock(HttpRules.class);
when(httpRules.get(anyString(), any())).thenReturn(httpRules);

// when
healthService.routing(httpRules);

// then
verify(httpRules, times(1)).get(eq(LIVENESS_PATH), any());
verify(httpRules, times(1)).get(eq(READINESS_PATH), any());
assertEquals(HEALTH_PATH, healthService.getHealthRootPath());
}
}

0 comments on commit a8bc5ec

Please sign in to comment.