diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh index 828a599ff7c..d5a85c7404a 100755 --- a/buildscripts/kokoro/unix.sh +++ b/buildscripts/kokoro/unix.sh @@ -64,28 +64,17 @@ if [[ -z "${SKIP_TESTS:-}" ]]; then # --batch-mode reduces log spam mvn verify --batch-mode popd - pushd examples/example-alts - ../gradlew build $GRADLE_FLAGS - popd - pushd examples/example-hostname - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-tls - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-jwt-auth - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-xds - ../gradlew build $GRADLE_FLAGS - popd + for f in examples/example-* + do + pushd "$f" + ../gradlew build $GRADLE_FLAGS + if [ -f "pom.xml" ]; then + # --batch-mode reduces log spam + mvn verify --batch-mode + fi + popd + done # TODO(zpencer): also build the GAE examples - pushd examples/example-orca - ../gradlew build $GRADLE_FLAGS - popd fi LOCAL_MVN_TEMP=$(mktemp -d) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 2a5ed52b35c..98b5c223ed3 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -35,6 +35,22 @@ java_grpc_library( deps = [":hello_streaming_java_proto"], ) +proto_library( + name = "echo_proto", + srcs = ["src/main/proto/echo.proto"], +) + +java_proto_library( + name = "echo_java_proto", + deps = [":recho_proto"], +) + +java_grpc_library( + name = "echo_java_grpc", + srcs = [":echo_proto"], + deps = [":echo_java_proto"], +) + proto_library( name = "route_guide_proto", srcs = ["src/main/proto/route_guide.proto"], @@ -68,6 +84,8 @@ java_library( ":hello_streaming_java_proto", ":helloworld_java_grpc", ":helloworld_java_proto", + ":echo_java_grpc", + ":echo_java_proto", ":route_guide_java_grpc", ":route_guide_java_proto", "@com_google_protobuf//:protobuf_java", @@ -172,3 +190,20 @@ java_binary( ], ) +java_binary( + name = "deadline-server", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineServer", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "deadline-client", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineClient", + runtime_deps = [ + ":examples", + ], +) diff --git a/examples/README.md b/examples/README.md index f57ad169897..ee43667a326 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,8 @@ before trying out the examples. - [Error handling](src/main/java/io/grpc/examples/errorhandling) +- [Client and Server Sharing](src/main/java/io/grpc/examples/multiplex) + - [Compression](src/main/java/io/grpc/examples/experimental) - [Flow control](src/main/java/io/grpc/examples/manualflowcontrol) @@ -117,6 +119,8 @@ before trying out the examples. +- [Keep Alive](src/main/java/io/grpc/examples/keepalive) + ### To build the examples 1. **[Install gRPC Java library SNAPSHOT locally, including code generation plugin](../COMPILING.md) (Only need this step for non-released versions, e.g. master HEAD).** diff --git a/examples/build.gradle b/examples/build.gradle index c45e8e9da54..4b45e134547 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,6 +167,48 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } +task multiplexingServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' + applicationName = 'multiplexing-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task sharingClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.SharingClient' + applicationName = 'sharing-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task deadlineServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineServer' + applicationName = 'deadline-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task deadlineClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineClient' + applicationName = 'deadline-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task keepAliveServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' + applicationName = 'keep-alive-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task keepAliveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' + applicationName = 'keep-alive-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + applicationDistribution.into('bin') { from(routeGuideServer) from(routeGuideClient) @@ -183,5 +225,13 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) + from(multiplexingServer) + from(sharingClient) + from(deadlineServer) + from(deadlineClient) + from(keepAliveServer) + from(keepAliveClient) + from(multiplexingServer) + from(sharingClient) fileMode = 0755 } diff --git a/examples/example-debug/README.md b/examples/example-debug/README.md new file mode 100644 index 00000000000..05ae14a189e --- /dev/null +++ b/examples/example-debug/README.md @@ -0,0 +1,239 @@ +# gRPC Debug Example + +The debug example uses a Hello World style server whose response includes its +hostname. It demonstrates usage of the AdminInterface and the grpcdebug +commandline tool. + +The example requires grpc-java to already be built. You are strongly encouraged +to check out a git release tag, since there will already be a build of grpc +available. Otherwise, you must follow [COMPILING](../../COMPILING.md). + +### Build the example + +1. Optional: Build the hello-world-debug example client. + See [the examples README](../README.md) + +2. Build the debuggable server and client. From the + `grpc-java/examples/examples-debug` directory run: + +```bash +$ ../gradlew installDist +``` + +This creates the +scripts `build/install/debug/bin/hostname-debuggable-server/bin/hostname-debuggable-server` +that +runs the example. + +To run the debug example, run: + +```bash +$ ./build/install/debug/bin/hostname-debuggable-server/bin/hostname-debuggable-server +``` + +And in a different terminal window run the client. + +Note: You can use the standard hello-world client with no debugging enabled and +still see results on the server. However, if you want to get debug information +about the client you need to run the hello-world-debuggable client. + +Simple client + +```bash +$ ../build/install/examples/bin/hello-world-client +``` + +debug enabled client + +```bash +$ ./build/install/examples-debug/bin/hello-world-debuggable-client +``` + +### Maven + +If you prefer to use Maven: + +1. Build the hello-world example client. See [the examples README](../README.md) + +2. Run in this directory: + +```bash +$ mvn verify +$ # Run the server (from the examples-debug directory) +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.debug.HostnameServer +$ # In another terminal run the client (from the examples directory) +$ cd .. +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldClient +``` + +## Using grpcdebug + +grpcdebug is a tool that has been created to access the metrics from the +channelz and health services. + +### Installing the grpcdebug tool + +The source code is located in a github project +[grpc-ecosystem/grpcdebug](https://github.com/grpc-ecosystem/grpcdebug). You +can either download [the latest built version] +(https://github.com/grpc-ecosystem/grpcdebug/releases/latest) (recommended) or +follow the README.md to build it yourself. + +### Running the grpcdebug tool +#### Usage +`grpcdebug [flags] channelz [argument]` + + +| Command | Argument | Description | +|:-----------|:--------------------:|:--------------------------------------------------| +| channel | \ | Display channel states in a human readable way. | +| channels | | Lists client channels for the target application. | +| server | \ | Displays server state in a human readable way. | +| servers | | Lists servers in a human readable way. | +| socket | \ | Displays socket states in a human readable way. | +| subchannel | \ | Display subchannel states in human readable way. | + +Generally, you will start with either `servers` or `channels` and then work down +to the details +

+ +#### Getting overall server info +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz servers +``` +This will show you the server ids with their activity +```text +Server ID Listen Addresses Calls(Started/Succeeded/Failed) Last Call Started +2 [[::]:50051] 38/34/3 now +``` +
+ +#### Getting details for a service +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz server 2 +``` + +The output will include more communication details and will show socket ids for +currently connected clients + +```text +Server Id: 2 +Listen Addresses: [[::]:50051] +Calls Started: 33 +Calls Succeeded: 29 +Calls Failed: 3 +Last Call Started: now +--- +Socket ID Local->Remote Streams(Started/Succeeded/Failed) Messages(Sent/Received) +19 [::1]:50051->[::1]:39834 4/3/0 3/4 +``` + +#### Displaying detailed info for a server side connection (socket) + +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz socket 19 +``` + +This will show a lot of gRPC internal information + +```text +Socket ID: 19 +Address: [::1]:50051->[::1]:50094 +Streams Started: 1 +Streams Succeeded: 0 +Streams Failed: 0 +Messages Sent: 0 +Messages Received: 1 +Keep Alives Sent: 0 +Last Local Stream Created: +Last Remote Stream Created: now +Last Message Sent Created: +Last Message Received Created: now +Local Flow Control Window: 65535 +Remote Flow Control Window: 1048569 +--- +Socket Options Name Value +SO_LINGER [type.googleapis.com/grpc.channelz.v1.SocketOptionLinger]:{} +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_CORK false +WRITE_BUFFER_HIGH_WATER_MARK 65536 +WRITE_BUFFER_LOW_WATER_MARK 32768 +IP_TOS 0 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPCNT 9 +SINGLE_EVENTEXECUTOR_PER_GROUP true +SO_SNDBUF 2626560 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_NOTSENT_LOWAT 0 +WRITE_BUFFER_WATER_MARK WriteBufferWaterMark(low: 32768, high: 65536) +TCP_NODELAY true +SO_RCVBUF 131072 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#SO_BUSY_POLL 0 +IP_TRANSPARENT false +SO_KEEPALIVE true +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_QUICKACK false +ALLOCATOR PooledByteBufAllocator(directByDefault: true) +TCP_FASTOPEN_CONNECT false +MESSAGE_SIZE_ESTIMATOR io.grpc.netty.shaded.io.netty.channel.DefaultMessageSizeEstimator@48d475b6 +WRITE_SPIN_COUNT 16 +SO_REUSEADDR true +CONNECT_TIMEOUT_MILLIS 30000 +ALLOW_HALF_CLOSURE false +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#EPOLL_MODE EDGE_TRIGGERED +MAX_MESSAGES_PER_READ 16 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPIDLE 7200 +AUTO_CLOSE true +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPINTVL 75 +MAX_MESSAGES_PER_WRITE 2147483647 +AUTO_READ true +TCP_MD5SIG null +RCVBUF_ALLOCATOR io.grpc.netty.shaded.io.netty.channel.AdaptiveRecvByteBufAllocator@360691a0 +``` +#### Displaying the list of gRPC client channels +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz channels +``` +Output +```text +Channel ID Target State Calls(Started/Succeeded/Failed) Created Time +1 localhost:50051 READY 34/34/0 +3 localhost:50051 READY 16/16/0 +``` +Note: If you have a simple server that doesn't use gRPC clients to contact other +servers, then this table will be empty. + +#### Displaying details of a gRPC client channel +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz channel 3 +``` +Output +```text +Channel ID: 3 +Target: localhost:50051 +State: READY +Calls Started: 16 +Calls Succeeded: 16 +Calls Failed: 0 +Created Time: +--- +Subchannel ID Target State Calls(Started/Succeeded/Failed) CreatedTime +10 [[[localhost/127.0.0.1:50051]/{}], [[localhost/0:0 READY 16/16/0 +``` + +#### Displaying details of a gRPC client subchannel +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz subchannel 10 +``` +Output +```text +Subchannel ID: 10 +Target: [[[localhost/127.0.0.1:50051]/{}], [[localhost/0:0:0:0:0:0:0:1:50051]/{}]] +State: READY +Calls Started: 16 +Calls Succeeded: 16 +Calls Failed: 0 +Created Time: +--- +Socket ID Local->Remote Streams(Started/Succeeded/Failed) Messages(Sent/Received) +11 127.0.0.1:48536->127.0.0.1:50051 16/16/0 12/12 +``` \ No newline at end of file diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle new file mode 100644 index 00000000000..6cdfd9ccdd8 --- /dev/null +++ b/examples/example-debug/build.gradle @@ -0,0 +1,76 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + id 'java' + + id "com.google.protobuf" version "0.8.17" + + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" } + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you +// are looking at a tagged version of the example and not "master"! + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def grpcVersion = '1.55.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protobufVersion = '3.21.7' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" + + testImplementation 'junit:junit:4.13.2' + testImplementation "io.grpc:grpc-testing:${grpcVersion}" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protobufVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } +} + +startScripts.enabled = false + +task HelloWorldDebuggableClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.debug.HelloWorldDebuggableClient' + applicationName = 'hello-world-debuggable-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task HostnameDebuggableServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.debug.HostnameDebuggableServer' + applicationName = 'hostname-debuggable-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(HelloWorldDebuggableClient) + from(HostnameDebuggableServer) + fileMode = 0755 +} diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml new file mode 100644 index 00000000000..fbfef978a5b --- /dev/null +++ b/examples/example-debug/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + io.grpc + example-debug + jar + + 1.55.0-SNAPSHOT + example-debug + https://github.com/grpc/grpc-java + + + UTF-8 + 1.55.0-SNAPSHOT + 3.21.7 + + 1.8 + 1.8 + + + + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-services + + + org.apache.tomcat + annotations-api + 6.0.53 + provided + + + io.grpc + grpc-netty-shaded + runtime + + + junit + junit + 4.13.2 + test + + + io.grpc + grpc-testing + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + enforce + + enforce + + + + + + + + + + + + diff --git a/examples/example-debug/settings.gradle b/examples/example-debug/settings.gradle new file mode 100644 index 00000000000..3700c983b6c --- /dev/null +++ b/examples/example-debug/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-debug' diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java new file mode 100644 index 00000000000..61391b60415 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.debug; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.services.AdminInterface; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A client that creates a channelz service and then requests a greeting 50 times. + * It uses 2 channels to communicate with the server one of which is shared by 2 stubs and + * one of which has only 1 stub. The requests are split over the 3 channels. + * Once completed, there is a 30 second sleep to allow more time to run the commandline debugger. + */ +public class HelloWorldDebuggableClient { + + private static final Logger logger = Logger.getLogger(HelloWorldDebuggableClient.class.getName()); + public static final int NUM_ITERATIONS = 50; + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the specified channel. */ + public HelloWorldDebuggableClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + String user = "world"; + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + int debugPort = 51051; + // Allow passing in the user and target strings as command line arguments + if (args.length > 0) { + if ("--help".equals(args[0])) { + System.err.println("Usage: [name [target]]"); + System.err.println(""); + System.err.println(" name The name you wish to be greeted by. Defaults to " + user); + System.err.println(" target The server to connect to. Defaults to " + target); + System.exit(1); + } + user = args[0]; + } + if (args.length > 1) { + target = args[1]; + } + + // Create a pair of communication channels to the server. Channels are thread-safe + // and reusable. + ManagedChannel channel1 = Grpc.newChannelBuilder(target, + InsecureChannelCredentials.create()).build(); + ManagedChannel channel2 = Grpc.newChannelBuilder(target, + InsecureChannelCredentials.create()).build(); + Server server = null; + try { + // Create a service from which grpcdebug can request debug info + server = Grpc.newServerBuilderForPort(debugPort, InsecureServerCredentials.create()) + .addServices(AdminInterface.getStandardServices()) + .build() + .start(); + + // Create the 3 clients + HelloWorldDebuggableClient client1 = new HelloWorldDebuggableClient(channel1); + HelloWorldDebuggableClient client2 = new HelloWorldDebuggableClient(channel1); + HelloWorldDebuggableClient client3 = new HelloWorldDebuggableClient(channel2); + + // Do the client requests spreadying them over the 3 clients + for (int i=0; i < NUM_ITERATIONS; i++) { + switch (i % 3) { + case 0: + client1.greet(user); + break; + case 1: + client2.greet(user); + break; + case 2: + client3.greet(user); + break; + } + } + System.out.println("Completed " + NUM_ITERATIONS + + " requests, will now sleep for 30 seconds to give some time for command line calls"); + Thread.sleep(30000); // Give some time for running grpcdebug + } finally { + // ManagedChannels use resources like threads and TCP connections. To prevent leaking these + // resources the channel should be shut down when it will no longer be used. If it may be used + // again leave it running. + channel1.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + channel2.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + + if (server != null) { + server.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + } +} diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java new file mode 100644 index 00000000000..89ffc39b599 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.debug; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.health.v1.HealthCheckResponse.ServingStatus; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.services.AdminInterface; +import io.grpc.services.HealthStatusManager; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * A server that hosts HostnameGreeter, plus the channelz service which grpcdebug uses. + */ +public final class HostnameDebuggableServer { + static int port = 50051; + static String hostname = null; + + public static void main(String[] args) throws IOException, InterruptedException { + parseArgs(args); // sets port and hostname + + final Server server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new HostnameGreeter(hostname)) + .addServices(AdminInterface.getStandardServices()) // the key add for enabling grpcdebug + .build() + .start(); + + System.out.println("Listening on port " + port); + + addShutdownHook(server); // Configures cleanup + server.awaitTermination(); // Block until shutdown + } + + private static void parseArgs(String[] args) { + if (args.length >= 1) { + try { + port = Integer.parseInt(args[0]); + } catch (NumberFormatException ex) { + System.err.println("Usage: [port [hostname]]"); + System.err.println(""); + System.err.println(" port The listen port. Defaults to " + port); + System.err.println(" hostname The name clients will see in greet responses. "); + System.err.println(" Defaults to the machine's hostname"); + System.exit(1); + } + } + if (args.length >= 2) { + hostname = args[1]; + } + } + + private static void addShutdownHook(final Server server) { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Start graceful shutdown + server.shutdown(); + try { + // Wait for RPCs to complete processing + if (!server.awaitTermination(30, TimeUnit.SECONDS)) { + // That was plenty of time. Let's cancel the remaining RPCs + server.shutdownNow(); + // shutdownNow isn't instantaneous, so give a bit of time to clean resources up + // gracefully. Normally this will be well under a second. + server.awaitTermination(5, TimeUnit.SECONDS); + } + } catch (InterruptedException ex) { + server.shutdownNow(); + } + } + }); + } +} diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java new file mode 100644 index 00000000000..7146f30cfe6 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.debug; + +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Greeter implementation which replies identifying itself with its hostname. */ +public final class HostnameGreeter extends GreeterGrpc.GreeterImplBase { + private static final Logger logger = Logger.getLogger(HostnameGreeter.class.getName()); + + private AtomicInteger callCount = new AtomicInteger(); + + private final String serverName; + + public HostnameGreeter(String serverName) { + if (serverName == null) { + serverName = determineHostname(); + } + this.serverName = serverName; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + int curCount = callCount.incrementAndGet(); + HelloReply reply = HelloReply.newBuilder() + .setMessage(String.format("Hello %s, from %s. You are requester number %d.", + req.getName(), serverName, curCount)) + .build(); + // Add a pause so that there is time to run debug commands + try { + int sleep_interval = (curCount % 10) * 100; // 0 - 1 second + Thread.sleep(sleep_interval); + } catch (InterruptedException e) { + responseObserver.onError(e); + } + // Send the response + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + private static String determineHostname() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (IOException ex) { + logger.log(Level.INFO, "Failed to determine hostname. Will generate one", ex); + } + // Strange. Well, let's make an identifier for ourselves. + return "generated-" + new Random().nextInt(); + } +} diff --git a/examples/example-debug/src/main/proto/helloworld/helloworld.proto b/examples/example-debug/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..c60d9416f1f --- /dev/null +++ b/examples/example-debug/src/main/proto/helloworld/helloworld.proto @@ -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.helloworld"; +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; +} diff --git a/examples/example-reflection/README.md b/examples/example-reflection/README.md new file mode 100644 index 00000000000..9bd91f3edb0 --- /dev/null +++ b/examples/example-reflection/README.md @@ -0,0 +1,106 @@ +gRPC Reflection Example +================ + +The reflection example has a Hello World server with `ProtoReflectionService` registered. + +### Build the example + +To build the example server, from the `grpc-java/examples/examples-reflection` +directory: +``` +$ ../gradlew installDist +``` + +This creates the scripts `build/install/example-reflection/bin/reflection-server`. + +### Run the example + +gRPC Server Reflection provides information about publicly-accessible gRPC services on a server, +and assists clients at runtime to construct RPC requests and responses without precompiled +service information. It is used by gRPCurl, which can be used to introspect server protos and +send/receive test RPCs. + +1. To start the reflection example server on its default port of 50051, run: +``` +$ ./build/install/example-reflection/bin/reflection-server +``` + +2. After enabling Server Reflection in a server application, you can use gRPCurl to check its +services. Instructions on how to install and use gRPCurl can be found at [gRPCurl Installation](https://github.com/fullstorydev/grpcurl#installation) + +After installing gRPCurl, open a new terminal and run the commands from the new terminal. + +### List all the services exposed at a given port + + ``` + $ grpcurl -plaintext localhost:50051 list + ``` + +Output + + ``` + grpc.reflection.v1alpha.ServerReflection + helloworld.Greeter + ``` + +### List all the methods of a service + ``` + $ grpcurl -plaintext localhost:50051 helloworld.Greeter + ``` +Output + ``` + helloworld.Greeter.SayHello + ``` + +### Describe services and methods + +The describe command inspects a method given its full name(in the format of +`..`). + + ``` +$ grpcurl -plaintext localhost:50051 describe helloworld.Greeter.SayHello + ``` + +Output + + ``` + helloworld.Greeter.SayHello is a method: + rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply ); + ``` + +### Inspect message types + +We can use the describe command to inspect request/response types given the full name of the type +(in the format of `.`). + +Get information about the request type: + + ``` +$ grpcurl -plaintext localhost:50051 describe helloworld.HelloRequest + ``` + +Output + + ``` + helloworld.HelloRequest is a message: + message HelloRequest { + string name = 1; + } + ``` + +### Call a remote method + +We can send RPCs to a server and get responses using the full method name +(in the format of `..`). The `-d ` flag represents the request data +and the -format text flag indicates that the request data is in text format. + + ``` + $ grpcurl -plaintext -format text -d 'name: "gRPCurl"' \ + localhost:50051 helloworld.Greeter.SayHello + ``` + +Output + + ``` + message: "Hello gRPCurl" + ``` diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle new file mode 100644 index 00000000000..d6aee016c16 --- /dev/null +++ b/examples/example-reflection/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions + id 'com.google.protobuf' version '0.8.17' + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' + id 'java' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" } + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +def grpcVersion = '1.55.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protocVersion = '3.21.7' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-netty-shaded:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + +} + +protobuf { + protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } + plugins { + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + } + generateProtoTasks { + all()*.plugins { grpc {} } + } +} + +startScripts.enabled = false + +task ReflectionServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.reflection.ReflectionServer' + applicationName = 'reflection-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(ReflectionServer) + fileMode = 0755 +} diff --git a/examples/example-reflection/settings.gradle b/examples/example-reflection/settings.gradle new file mode 100644 index 00000000000..dccb973085e --- /dev/null +++ b/examples/example-reflection/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-reflection' diff --git a/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java b/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java new file mode 100644 index 00000000000..ad702247ba7 --- /dev/null +++ b/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java @@ -0,0 +1,81 @@ + +package io.grpc.examples.reflection; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Server that manages startup/shutdown of a {@code Greeter} server. + */ +public class ReflectionServer { + private static final Logger logger = Logger.getLogger(ReflectionServer.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new GreeterImpl()) + .addService(ProtoReflectionService.newInstance()) // add reflection service + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + ReflectionServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final ReflectionServer server = new ReflectionServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/example-reflection/src/main/proto/helloworld/helloworld.proto b/examples/example-reflection/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..77184aa8326 --- /dev/null +++ b/examples/example-reflection/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2022 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.helloworld"; +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; +} diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 64b0a8eac10..f6772cda51d 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -23,7 +23,7 @@ dependencies { "io.grpc:grpc-servlet:${grpcVersion}", "io.grpc:grpc-stub:${grpcVersion}" - providedImplementation "javax.servlet:javax.servlet-api:4.0.1", + compileOnly "javax.servlet:javax.servlet-api:4.0.1", "org.apache.tomcat:annotations-api:6.0.53" } diff --git a/examples/logging.properties b/examples/logging.properties new file mode 100644 index 00000000000..b807613adcd --- /dev/null +++ b/examples/logging.properties @@ -0,0 +1,8 @@ +# Create a file called logging.properties with the following contents. +handlers=java.util.logging.ConsoleHandler +io.grpc.level=FINE +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + +# Pass the location of the file to JVM via this command-line flag +JAVA_OPTS=-Djava.util.logging.config.file=logging.properties diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java new file mode 100644 index 00000000000..378688ee1cc --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.deadline; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.helloworld.HelloWorldServer; +import java.util.concurrent.TimeUnit; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * A simple client that requests a greeting from the {@link HelloWorldServer}. + * + *

This is based off the client in the helloworld example with some deadline logic added. + */ +public class DeadlineClient { + private static final Logger logger = Logger.getLogger(DeadlineClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public DeadlineClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public Status greet(String name, long timeoutMillis) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.withDeadlineAfter(timeoutMillis, TimeUnit.MILLISECONDS) + .sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return e.getStatus(); + } + logger.info("Greeting: " + response.getMessage()); + return Status.OK; + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + System.setProperty("java.util.logging.SimpleFormatter.format", + "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); + + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); + try { + DeadlineClient client = new DeadlineClient(channel); + + // The server takes 500ms to process the call, so setting a deadline further in the future we + // should get a successful response. + logger.info("Calling server with a generous deadline, expected to work"); + client.greet("deadline client", 1000); + + // A smaller deadline will result in us getting a DEADLINE_EXCEEDED error. + logger.info( + "Calling server with an unrealistic (300ms) deadline, expecting a DEADLINE_EXCEEDED"); + client.greet("deadline client", 300); + + // Including the "propagate" magic string in the request will cause the server to call itself + // to simulate a situation where a server needs to call another server to satisfy the original + // request. This will double the time it takes to respond to the client request, but with + // an increased deadline we should get a successful response. + logger.info("Calling server with propagation and a generous deadline, expected to work"); + client.greet("deadline client [propagate]", 2000); + + // With this propagated call we reduce the deadline making it impossible for the both the + // first server call and the propagated one to succeed. You should see the call fail with + // DEADLINE_EXCEEDED, and you should also see DEADLINE_EXCEEDED in the server output as it + // runs out of time waiting for the propagated call to finish. + logger.info( + "Calling server with propagation and a generous deadline, expecting a DEADLINE_EXCEEDED"); + client.greet("deadline client [propagate]", 1000); + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java new file mode 100644 index 00000000000..49214fc77d5 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java @@ -0,0 +1,119 @@ + +/* + * Copyright 2023 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. + */ +package io.grpc.examples.deadline; + +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class DeadlineServer { + private static final Logger logger = Logger.getLogger(DeadlineServer.class.getName()); + + private Server server; + + + private void start() throws IOException { + int port = 50051; + SlowGreeter slowGreeter = new SlowGreeter(); + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(slowGreeter) + .build() + .start(); + logger.info("Server started, listening on " + port); + + // Create a channel to this same server so we can make a recursive call to demonstrate deadline + // propagation. + String target = "localhost:50051"; + slowGreeter.setClientStub(GreeterGrpc.newBlockingStub( + Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build())); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DeadlineServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + System.setProperty("java.util.logging.SimpleFormatter.format", + "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); + + final DeadlineServer server = new DeadlineServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class SlowGreeter extends GreeterGrpc.GreeterImplBase { + private GreeterGrpc.GreeterBlockingStub clientStub; + + void setClientStub(GreeterGrpc.GreeterBlockingStub clientStub) { + this.clientStub = clientStub; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + + if (req.getName().contains("propagate")) { + clientStub.sayHello(HelloRequest.newBuilder().setName("Server").build()); + } + + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java new file mode 100644 index 00000000000..a7c59c3952f --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.keepalive; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A simple client that requests a greeting from the {@link KeepAliveServer}. + */ +public class KeepAliveClient { + private static final Logger logger = Logger.getLogger(KeepAliveClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public KeepAliveClient(Channel channel) { + // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to + // shut it down. + + // Passing Channels to code makes code easier to test and makes it easier to reuse Channels. + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + /** + * Greet server. + */ + public static void main(String[] args) throws Exception { + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + + // Create a channel with the following keep alive configurations (demo only, you should set + // more appropriate values based on your environment): + // keepAliveTime: Send pings every 10 seconds if there is no activity. Set to an appropriate + // value in reality, e.g. (5, TimeUnit.MINUTES). + // keepAliveTimeout: Wait 1 second for ping ack before considering the connection dead. Set to a + // larger value in reality, e.g. (10, TimeUnit.SECONDS). You should only set such a small value, + // e.g. (1, TimeUnit.SECONDS) in certain low latency environments. + // keepAliveWithoutCalls: Send pings even without active streams. Normally disable it. + // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see the keep alive ping + // frames. + // More details see: https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .keepAliveTime(5, TimeUnit.MINUTES) + .keepAliveTime(10, TimeUnit.SECONDS) // Change to a larger value, e.g. 5min. + .keepAliveTimeout(1, TimeUnit.SECONDS) // Change to a larger value, e.g. 10s. + .keepAliveWithoutCalls(true)// You should normally avoid enabling this. + .build(); + + try { + KeepAliveClient client = new KeepAliveClient(channel); + client.greet("Keep-alive Demo"); + Thread.sleep(30000); + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java new file mode 100644 index 00000000000..884bbfea532 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 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. + */ + +package io.grpc.examples.keepalive; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Server that manages startup/shutdown of a keep alive server. + */ +public class KeepAliveServer { + private static final Logger logger = Logger.getLogger(KeepAliveServer.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + + // Start a server with the following configurations (demo only, you should set more appropriate + // values based on your real environment): + // keepAliveTime: Ping the client if it is idle for 5 seconds to ensure the connection is + // still active. Set to an appropriate value in reality, e.g. in minutes. + // keepAliveTimeout: Wait 1 second for the ping ack before assuming the connection is dead. + // Set to an appropriate value in reality, e.g. (10, TimeUnit.SECONDS). + // permitKeepAliveTime: If a client pings more than once every 5 seconds, terminate the + // connection. + // permitKeepAliveWithoutCalls: Allow pings even when there are no active streams. + // maxConnectionIdle: If a client is idle for 15 seconds, send a GOAWAY. + // maxConnectionAge: If any connection is alive for more than 30 seconds, send a GOAWAY. + // maxConnectionAgeGrace: Allow 5 seconds for pending RPCs to complete before forcibly closing + // connections. + // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see keep alive ping + // frames. + // More details see: https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new GreeterImpl()) + .keepAliveTime(5, TimeUnit.SECONDS) + .keepAliveTimeout(1, TimeUnit.SECONDS) + .permitKeepAliveTime(5, TimeUnit.SECONDS) + .permitKeepAliveWithoutCalls(true) + .maxConnectionIdle(15, TimeUnit.SECONDS) + .maxConnectionAge(30, TimeUnit.SECONDS) + .maxConnectionAgeGrace(5, TimeUnit.SECONDS) + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + KeepAliveServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final KeepAliveServer server = new KeepAliveServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java new file mode 100644 index 00000000000..cf8f6fdb7fe --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java @@ -0,0 +1,100 @@ +/* + * 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. + */ + +package io.grpc.examples.multiplex; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Server that manages startup/shutdown of a {@code Greeter} server. + */ +public class EchoServer extends EchoGrpc.EchoImplBase{ + private static final Logger logger = Logger.getLogger(EchoServer.class.getName()); + + @Override + public void unaryEcho(EchoRequest request, + StreamObserver responseObserver) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + } + + @Override + public void serverStreamingEcho(EchoRequest request, + StreamObserver responseObserver) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver clientStreamingEcho( + final StreamObserver responseObserver) { + return new StreamObserver() { + List requestList = new ArrayList<>(); + + @Override + public void onNext(EchoRequest request) { + requestList.add(request.getMessage()); + } + + @Override + public void onError(Throwable t) { + logger.log(Level.WARNING, "echo stream cancelled"); + } + + @Override + public void onCompleted() { + String reply = requestList.stream().collect(Collectors.joining(", ")); + EchoResponse response = EchoResponse.newBuilder().setMessage(reply).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + }; + } + + @Override + public StreamObserver bidirectionalStreamingEcho( + final StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(EchoRequest request) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + } + + @Override + public void onError(Throwable t) { + logger.log(Level.WARNING, "echo stream cancelled"); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } +} diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java new file mode 100644 index 00000000000..f5607c287f6 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package io.grpc.examples.multiplex; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * A sample gRPC server that serves both the Greeting and Echo services. + */ +public class MultiplexingServer { + + private static final Logger logger = Logger.getLogger(MultiplexingServer.class.getName()); + + private final int port; + private Server server = null; + + public MultiplexingServer(int port) throws IOException { + this.port = port; + } + private void start() throws IOException { + /* The port on which the server should run */ + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new io.grpc.examples.multiplex.MultiplexingServer.GreeterImpl()) + .addService(new io.grpc.examples.multiplex.EchoServer()) + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + io.grpc.examples.multiplex.MultiplexingServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final MultiplexingServer server = new MultiplexingServer(50051); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } + } diff --git a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java new file mode 100644 index 00000000000..dd087f4a04b --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java @@ -0,0 +1,195 @@ + +/* + * 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. + */ + +package io.grpc.examples.multiplex; + +import com.google.common.collect.ImmutableList; +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; +import io.grpc.examples.helloworld.HelloWorldClient; +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A client that shares a channel across multiple stubs to a single service and across services + * being provided by one server process. + */ +public class SharingClient { + private static final Logger logger = Logger.getLogger( + HelloWorldClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub greeterStub1; + private final GreeterGrpc.GreeterBlockingStub greeterStub2; + private final EchoGrpc.EchoStub echoStub; + + private Random random = new Random(); + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public SharingClient(Channel channel) { + // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to + // shut it down. + + // Passing Channels to code makes code easier to test and makes it easier to reuse Channels. + greeterStub1 = GreeterGrpc.newBlockingStub(channel); + greeterStub2 = GreeterGrpc.newBlockingStub(channel); + echoStub = EchoGrpc.newStub(channel); + } + + /** Say hello to server. */ + private void greet(String name, GreeterGrpc.GreeterBlockingStub stub, String stubName) { + logger.info("Will try to greet " + name + " using " + stubName); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = stub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + public void greet1(String name) { + greet(name, greeterStub1, "greeter #1"); + } + + public void greet2(String name) { + greet(name, greeterStub2, "greeter #2"); + } + + public CountDownLatch initiateEchos(List valuesToSend, final List valuesReceived) { + final CountDownLatch finishLatch = new CountDownLatch(1); + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onNext(EchoResponse response) { + valuesReceived.add(response.getMessage()); + } + + @Override + public void onError(Throwable t) { + logger.warning("Echo Failed: {0}" + Status.fromThrowable(t)); + finishLatch.countDown(); + } + + @Override + public void onCompleted() { + logger.info("Finished RecordRoute"); + finishLatch.countDown(); + } + }; + + StreamObserver requestObserver = + echoStub.bidirectionalStreamingEcho(responseObserver); + try { + for (String curValue : valuesToSend) { + EchoRequest req = EchoRequest.newBuilder().setMessage(curValue).build(); + requestObserver.onNext(req); + // Sleep for a bit before sending the next one. + Thread.sleep(random.nextInt(1000) + 500); + if (finishLatch.getCount() == 0) { + // RPC completed or errored before we finished sending. + // Sending further requests won't error, but they will just be thrown away. + return finishLatch; + } + } + } catch (RuntimeException e) { + // Cancel RPC + requestObserver.onError(e); + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + requestObserver.onError(e); + } + // Mark the end of requests + requestObserver.onCompleted(); + + return finishLatch; + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + String user = "world"; + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + // Allow passing in the user and target strings as command line arguments + if (args.length > 0) { + if ("--help".equals(args[0])) { + System.err.println("Usage: [name [target]]"); + System.err.println(""); + System.err.println(" name The name you wish to be greeted by. Defaults to " + user); + System.err.println(" target The server to connect to. Defaults to " + target); + System.exit(1); + } + user = args[0]; + } + if (args.length > 1) { + target = args[1]; + } + + // Create a communication channel to the server, known as a Channel. Channels are thread-safe + // and reusable. It is common to create channels at the beginning of your application and reuse + // them until the application shuts down. + // + // For the example we use plaintext insecure credentials to avoid needing TLS certificates. To + // use TLS, use TlsChannelCredentials instead. + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); + List echoInput = ImmutableList.of("some", "thing", "wicked", "this", "way", "comes"); + List echoOutput = new ArrayList<>(); + try { + SharingClient client = new SharingClient(channel); + + CountDownLatch finishLatch = client.initiateEchos(echoInput, echoOutput); + client.greet1(user + " the great"); + client.greet2(user + " the lesser"); + client.greet1(user + " the humble"); + // Receiving happens asynchronously + if (!finishLatch.await(1, TimeUnit.MINUTES)) { + logger.warning("Echo did not finish within 1 minute"); + } + + System.out.println("The echo requests and results were:"); + System.out.println(echoInput.toString()); + System.out.println(echoOutput.toString()); + } finally { + // ManagedChannels use resources like threads and TCP connections. To prevent leaking these + // resources the channel should be shut down when it will no longer be used. If it may be used + // again leave it running. + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/proto/echo.proto b/examples/src/main/proto/echo.proto new file mode 100644 index 00000000000..0668eb55bcc --- /dev/null +++ b/examples/src/main/proto/echo.proto @@ -0,0 +1,48 @@ +/* + * + * Copyright 2023 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.echo"; +option java_outer_classname = "EchoProto"; +option objc_class_prefix = "ECHO"; + +package echo; + +// EchoRequest is the request for echo. +message EchoRequest { + string message = 1; +} + +// EchoResponse is the response for echo. +message EchoResponse { + string message = 1; +} + +// Echo is the echo service. +service Echo { + // UnaryEcho is unary echo. + rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} + // ServerStreamingEcho is server side streaming. + rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} + // ClientStreamingEcho is client side streaming. + rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} + // BidirectionalStreamingEcho is bidi streaming. + rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} +} \ No newline at end of file diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle index 85e6e9eeb01..63850efc4e8 100644 --- a/gcp-observability/build.gradle +++ b/gcp-observability/build.gradle @@ -20,11 +20,13 @@ tasks.named("compileJava").configure { } dependencies { - def cloudLoggingVersion = '3.6.1' + def cloudLoggingVersion = '3.14.5' annotationProcessor libraries.auto.value api project(':grpc-api') - + + // TODO(dnvindhya): Prefer using our own libraries, update the dependencies + // in gradle/libs.versions instead implementation project(':grpc-protobuf'), project(':grpc-stub'), project(':grpc-alts'), @@ -35,12 +37,10 @@ dependencies { libraries.opencensus.exporter.trace.stackdriver, project(':grpc-xds'), // Align grpc versions project(':grpc-services'), // Align grpc versions - libraries.animalsniffer.annotations, // Prefer our version - libraries.google.auth.credentials, // Prefer our version - libraries.protobuf.java.util, // Prefer our version - libraries.gson, // Prefer our version - libraries.perfmark.api, // Prefer our version - libraries.re2j, // Prefer our version + ('com.google.protobuf:protobuf-java:3.21.12'), + ('com.google.api.grpc:proto-google-common-protos:2.14.2'), + ('com.google.auth:google-auth-library-oauth2-http:1.16.0'), + ('io.opencensus:opencensus-api:0.31.1'), ('com.google.guava:guava:31.1-jre') runtimeOnly libraries.opencensus.impl diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java index 9b46699efaf..abd44c43650 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java @@ -23,6 +23,7 @@ import com.google.common.base.Joiner; import com.google.protobuf.ByteString; import com.google.protobuf.Duration; +import com.google.rpc.Code; import io.grpc.Attributes; import io.grpc.Deadline; import io.grpc.Grpc; @@ -182,7 +183,7 @@ void logTrailer( PayloadBuilderHelper pair = createMetadataProto(metadata, maxHeaderBytes); - pair.payloadBuilder.setStatusCode(status.getCode().value()); + pair.payloadBuilder.setStatusCode(Code.forNumber(status.getCode().value())); String statusDescription = status.getDescription(); if (statusDescription != null) { pair.payloadBuilder.setStatusMessage(statusDescription); @@ -404,10 +405,10 @@ static Address socketAddressToProto(SocketAddress address) { if (address instanceof InetSocketAddress) { InetAddress inetAddress = ((InetSocketAddress) address).getAddress(); if (inetAddress instanceof Inet4Address) { - builder.setType(Address.Type.TYPE_IPV4) + builder.setType(Address.Type.IPV4) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else if (inetAddress instanceof Inet6Address) { - builder.setType(Address.Type.TYPE_IPV6) + builder.setType(Address.Type.IPV6) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else { logger.log(Level.SEVERE, "unknown type of InetSocketAddress: {}", address); @@ -417,7 +418,7 @@ static Address socketAddressToProto(SocketAddress address) { } else if (address.getClass().getName().equals("io.netty.channel.unix.DomainSocketAddress")) { // To avoid a compiled time dependency on grpc-netty, we check against the // runtime class name. - builder.setType(Address.Type.TYPE_UNIX) + builder.setType(Address.Type.UNIX) .setAddress(address.toString()); } else { builder.setType(Address.Type.TYPE_UNKNOWN).setAddress(address.toString()); diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java index e91f310e647..02ff4049eb4 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java @@ -18,12 +18,15 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.gax.batching.BatchingSettings; +import com.google.api.gax.batching.FlowController; import com.google.cloud.MonitoredResource; import com.google.cloud.logging.LogEntry; import com.google.cloud.logging.Logging; import com.google.cloud.logging.LoggingOptions; import com.google.cloud.logging.Payload.JsonPayload; import com.google.cloud.logging.Severity; +import com.google.cloud.logging.v2.stub.LoggingServiceV2StubSettings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -41,6 +44,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import org.threeten.bp.Duration; /** * Sink for Google Cloud Logging. @@ -102,6 +106,7 @@ public void write(GrpcLogRecord logProto) { if (servicesToExclude.contains(logProto.getServiceName())) { return; } + LogEntry grpcLogEntry = null; try { GrpcLogRecord.EventType eventType = logProto.getType(); // TODO(DNVindhya): make sure all (int, long) values are not displayed as double @@ -117,11 +122,18 @@ public void write(GrpcLogRecord logProto) { if (!customTags.isEmpty()) { grpcLogEntryBuilder.setLabels(customTags); } - LogEntry grpcLogEntry = grpcLogEntryBuilder.build(); + grpcLogEntry = grpcLogEntryBuilder.build(); synchronized (this) { logger.log(Level.FINEST, "Writing gRPC event : {0} to Cloud Logging", eventType); gcpLoggingClient.write(Collections.singleton(grpcLogEntry)); } + } catch (FlowController.FlowControlRuntimeException e) { + String grpcLogEntryString = null; + if (grpcLogEntry != null) { + grpcLogEntryString = grpcLogEntry.toStructuredJsonString(); + } + logger.log(Level.SEVERE, "Limit exceeded while writing log entry to cloud logging"); + logger.log(Level.SEVERE, "Log entry = ", grpcLogEntryString); } catch (Exception e) { logger.log(Level.SEVERE, "Caught exception while writing to Cloud Logging", e); } @@ -132,6 +144,16 @@ Logging createLoggingClient() { if (!Strings.isNullOrEmpty(projectId)) { builder.setProjectId(projectId); } + BatchingSettings loggingDefaultBatchingSettings = LoggingServiceV2StubSettings.newBuilder() + .writeLogEntriesSettings().getBatchingSettings(); + // Custom batching settings + BatchingSettings grpcLoggingVBatchingSettings = loggingDefaultBatchingSettings.toBuilder() + .setDelayThreshold(Duration.ofSeconds(1L)).setFlowControlSettings( + loggingDefaultBatchingSettings.getFlowControlSettings().toBuilder() + .setMaxOutstandingRequestBytes(52428800L) //50 MiB + .setLimitExceededBehavior(FlowController.LimitExceededBehavior.ThrowException) + .build()).build(); + builder.setBatchingSettings(grpcLoggingVBatchingSettings); return builder.build().getService(); } diff --git a/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto b/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto index 85ef00ac2dd..7d278aa08a9 100644 --- a/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto +++ b/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto @@ -20,6 +20,7 @@ package grpc.observabilitylog.v1; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; +import "google/rpc/code.proto"; option java_multiple_files = true; option java_package = "io.grpc.observabilitylog.v1"; @@ -97,7 +98,7 @@ message Payload { // the RPC timeout value google.protobuf.Duration timeout = 2; // The gRPC status code - uint32 status_code = 3; + google.rpc.Code status_code = 3; // The gRPC status message string status_message = 4; // The value of the grpc-status-details-bin metadata key, if any. @@ -115,9 +116,9 @@ message Payload { message Address { enum Type { TYPE_UNKNOWN = 0; - TYPE_IPV4 = 1; // in 1.2.3.4 form - TYPE_IPV6 = 2; // IPv6 canonical form (RFC5952 section 4) - TYPE_UNIX = 3; // UDS string + IPV4 = 1; // in 1.2.3.4 form + IPV6 = 2; // IPv6 canonical form (RFC5952 section 4) + UNIX = 3; // UDS string } Type type = 1; string address = 2; diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java index 73704eb4181..a6d9fab702f 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java @@ -29,6 +29,7 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; +import com.google.rpc.Code; import io.grpc.Attributes; import io.grpc.Grpc; import io.grpc.Metadata; @@ -94,7 +95,7 @@ public void socketToProto_ipv4() throws Exception { assertThat(LogHelper.socketAddressToProto(socketAddress)) .isEqualTo(Address .newBuilder() - .setType(Address.Type.TYPE_IPV4) + .setType(Address.Type.IPV4) .setAddress("127.0.0.1") .setIpPort(12345) .build()); @@ -109,7 +110,7 @@ public void socketToProto_ipv6() throws Exception { assertThat(LogHelper.socketAddressToProto(socketAddress)) .isEqualTo(Address .newBuilder() - .setType(Address.Type.TYPE_IPV6) + .setType(Address.Type.IPV6) .setAddress("2001:db8::2:1") // RFC 5952 section 4: ipv6 canonical form required .setIpPort(12345) .build()); @@ -454,7 +455,7 @@ public void logTrailer() throws Exception { builder.setPeer(LogHelper.socketAddressToProto(peer)); builder.setPayload( builder.getPayload().toBuilder() - .setStatusCode(Status.INTERNAL.getCode().value()) + .setStatusCode(Code.forNumber(Status.INTERNAL.getCode().value())) .setStatusMessage("test description") .build()); GrpcLogRecord base = builder.build();