Skip to content

Commit

Permalink
Add grpc-health service implementation (#2147)
Browse files Browse the repository at this point in the history
Motivation:
The gRPC community has defined a health checking standard [1]
and API [2]. ServiceTalk should provide a default implementation
of this service.

[1] https://github.com/grpc/grpc/blob/master/doc/health-checking.md
[2] https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
  • Loading branch information
Scottmitch authored Mar 15, 2022
1 parent 17c2bb4 commit b274746
Show file tree
Hide file tree
Showing 18 changed files with 874 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Compression[Compression]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Deadlines[Deadlines]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Observer[Observer]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Health[Health Checking]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#errors[Application Errors]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#execution-strategy[Execution Strategy]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#route-guide[Route Guide]
Expand Down
15 changes: 14 additions & 1 deletion servicetalk-examples/docs/modules/ROOT/pages/grpc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ Extends the blocking "Hello World" example to demonstrate configuration of debug
* link:{source-root}/servicetalk-examples/grpc/debugging/src/main/java/io/servicetalk/examples/grpc/debugging/DebuggingClient.java[DebuggingClient]
– Sends hello requests to the server and receives a greeting response.


[#Observer]
== Observer
This example demonstrates the following:
Expand All @@ -122,6 +121,20 @@ on the server builder.
link:{source-root}/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcLifecycleObserver.java[GrpcLifecycleObserver]
on via a client filter on the client builder.

[#Health]
== Health Checking
This example demonstrates the following:
- Use of
link:{source-root}/servicetalk-grpc-health/src/main/java/io/servicetalk/grpc/health/DefaultHealthService.java[DefaultHealthService]
which implements link:https://github.com/grpc/grpc/blob/master/doc/health-checking.md[gRPC health checking] paired with a simple "hello world" service.

Using the following classes:
* link:{source-root}/servicetalk-examples/grpc/health/src/main/java/io/servicetalk/examples/grpc/health/HealthServerExample.java[HealthServerExample] a server
that installs link:{source-root}/servicetalk-grpc-health/src/main/java/io/servicetalk/grpc/health/DefaultHealthService.java[DefaultHealthService] in addition to
a simple "hello world" service.
* link:{source-root}/servicetalk-examples/grpc/health/src/main/java/io/servicetalk/examples/grpc/health/HealthClientExample.java[HealthClientExample] a client
that calls the "hello world" server, the "health check" server, and prints results.

[#errors]
== Application Errors
The gRPC protocol supports propagating application level errors, and also provides serialization/deserialization of
Expand Down
3 changes: 3 additions & 0 deletions servicetalk-examples/grpc/health/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
== ServiceTalk gRPC Health Example

Extends "Hello World" ServiceTalk gRPC example to demonstrate default health service.
81 changes: 81 additions & 0 deletions servicetalk-examples/grpc/health/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project authors
*
* 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.
*/

buildscript {
dependencies {
classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufGradlePluginVersion"
}
}

apply plugin: "java"
apply plugin: "com.google.protobuf"
apply from: "../../gradle/idea.gradle"

dependencies {
implementation project(":servicetalk-annotations")
implementation project(":servicetalk-grpc-netty")
implementation project(":servicetalk-grpc-protoc")
implementation project(":servicetalk-grpc-protobuf")
implementation project(":servicetalk-grpc-health")

implementation "org.slf4j:slf4j-api:$slf4jVersion"
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobufVersion"
}

//// REMOVE if outside of ServiceTalk gradle project
def pluginJar = file("${project.rootProject.rootDir}/servicetalk-grpc-protoc/build" +
"/buildExecutable/servicetalk-grpc-protoc-${project.version}-all.jar")
//// REMOVE if outside of ServiceTalk gradle project

plugins {
servicetalk_grpc {
//// REMOVE if outside of ServiceTalk gradle project - use "artifact" as demonstrated below
//// "path" is used only because we want to use the gradle project local version of the plugin.
path = pluginJar.path
//// REMOVE if outside of ServiceTalk gradle project - use "artifact" as demonstrated below

// artifact = "io.servicetalk:servicetalk-grpc-protoc:$serviceTalkVersion:all@jar"
}
}
generateProtoTasks {
all().each { task ->
//// REMOVE if outside of ServiceTalk gradle project
task.dependsOn(":servicetalk-grpc-protoc:buildExecutable") // use gradle project local grpc-protoc dependency

// you may need to manually add the artifact name as an input
task.inputs
.file(pluginJar)
.withNormalizer(ClasspathNormalizer)
.withPropertyName("servicetalkPluginJar")
.withPathSensitivity(PathSensitivity.RELATIVE)
//// REMOVE if outside of ServiceTalk gradle project

task.plugins {
servicetalk_grpc {
// Need to tell protobuf-gradle-plugin to output in the correct directory if all generated
// code for a single proto goes to a single file (e.g. "java_multiple_files = false" in the .proto).
outputSubDir = "java"
}
}
}
}
generatedFilesBaseDir = "$buildDir/generated/sources/proto"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project authors
*
* 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 io.servicetalk.examples.grpc.health;

import io.servicetalk.grpc.api.GrpcStatusException;
import io.servicetalk.grpc.health.DefaultHealthService;
import io.servicetalk.grpc.netty.GrpcClients;
import io.servicetalk.health.v1.Health;
import io.servicetalk.health.v1.Health.BlockingHealthClient;
import io.servicetalk.health.v1.HealthCheckRequest;
import io.servicetalk.health.v1.HealthCheckResponse;

import io.grpc.examples.health.Greeter;
import io.grpc.examples.health.Greeter.BlockingGreeterClient;
import io.grpc.examples.health.HelloReply;
import io.grpc.examples.health.HelloRequest;

/**
* Extends the async "Hello World" example to demonstrate {@link DefaultHealthService} usage.
*/
public final class HealthClientExample {
public static void main(String... args) throws Exception {
final String serviceName = "World";
try (BlockingGreeterClient client = GrpcClients.forAddress("localhost", 8080)
.buildBlocking(new Greeter.ClientFactory());
BlockingHealthClient healthClient = GrpcClients.forAddress("localhost", 8080)
.buildBlocking(new Health.ClientFactory())) {
// Check health before
checkHealth(healthClient, serviceName);

HelloReply reply = client.sayHello(HelloRequest.newBuilder().setName("World").build());
System.out.println("HelloReply=" + reply.getMessage());

// Check the health after to observe it changed.
checkHealth(healthClient, serviceName);
}
}

private static void checkHealth(BlockingHealthClient healthClient, String serviceName) throws Exception {
try {
HealthCheckResponse response = healthClient.check(
HealthCheckRequest.newBuilder().setService(serviceName).build());
System.out.println("Service '" + serviceName + "' health=" + response.getStatus());
} catch (GrpcStatusException e) {
System.out.println("Service '" + serviceName + "' health exception=" + e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project authors
*
* 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 io.servicetalk.examples.grpc.health;

import io.servicetalk.grpc.health.DefaultHealthService;
import io.servicetalk.grpc.netty.GrpcServers;

import io.grpc.examples.health.Greeter.GreeterService;
import io.grpc.examples.health.HelloReply;

import static io.servicetalk.concurrent.api.Single.succeeded;
import static io.servicetalk.health.v1.HealthCheckResponse.ServingStatus.SERVING;

/**
* A simple extension of the gRPC "Hello World" example which demonstrates {@link DefaultHealthService}.
*/
public final class HealthServerExample {
public static void main(String... args) throws Exception {
DefaultHealthService healthService = new DefaultHealthService();
GreeterService greeterService = (ctx, request) -> {
// For demonstration purposes, just use the name as a service and mark it as SERVING.
healthService.setStatus(request.getName(), SERVING);
return succeeded(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
};
GrpcServers.forPort(8080)
.listenAndAwait(healthService, greeterService)
.awaitShutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project authors
*
* 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.
*/
@ElementsAreNonnullByDefault
package io.servicetalk.examples.grpc.health;

import io.servicetalk.annotations.ElementsAreNonnullByDefault;
37 changes: 37 additions & 0 deletions servicetalk-examples/grpc/health/src/main/proto/helloworld.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2015 The gRPC Authors
//
// 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.
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.health";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}
35 changes: 35 additions & 0 deletions servicetalk-examples/grpc/health/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright © 2018, 2021 Apple Inc. and the ServiceTalk project authors
~
~ 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.
-->
<Configuration status="info">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %30t [%-5level] %-30logger{1} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<!-- Prints server start and shutdown -->
<Logger name="io.servicetalk.http.netty.H2ServerParentConnectionContext" level="DEBUG"/>

<!-- Prints default subscriber errors-->
<Logger name="io.servicetalk.concurrent.api" level="DEBUG"/>

<!-- Use `-Dservicetalk.logger.level=DEBUG` to change the root logger level via command line -->
<Root level="${sys:servicetalk.logger.level:-INFO}">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ final class ServiceTalkLibraryPlugin extends ServiceTalkCorePlugin {
incrementalAnalysis = true
ruleSets = []
ruleSetConfig = resources.text.fromString(getClass().getResourceAsStream("pmd/basic.xml").text)

pmdMain {
excludes = [
'**/src/generated/**'
]
}
}

tasks.withType(Pmd).all {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@
<suppress checks="LineLength" files="docs[\\/]modules[\\/]ROOT[\\/]pages[\\/].*\.adoc"/>
<suppress checks="LineLength" files="docs[\\/]generation[\\/]supplemental-ui[\\/]partials[\\/].*\.hbs"/>
<suppress checks="LineLength" files="docs[\\/]generation[\\/]site-remote.yml"/>

<!-- generated -->
<suppress checks="." files=".*[\\/]src[\\/]generated[\\/].*"/>
</suppressions>
Loading

0 comments on commit b274746

Please sign in to comment.