diff --git a/examples/example-xds/README.md b/examples/example-xds/README.md index 22aa44004d..f3b0367390 100644 --- a/examples/example-xds/README.md +++ b/examples/example-xds/README.md @@ -1,9 +1,9 @@ gRPC XDS Example ================ -The XDS example is a Hello World client capable of being configured with the -XDS management protocol. Out-of-the-box it behaves the same as hello world -client. +The XDS example consists of a Hello World client and a Hello World server capable of +being configured with the XDS management protocol. Out-of-the-box they behave the same +as their hello-world version. __XDS support is incomplete and experimental, with limited compatibility. It will be very hard to produce a working enviornment just by this example. Please @@ -12,40 +12,60 @@ environment.__ ### Build the example -1. Build the hello-world example server or the hostname example server. See - [the examples README](../README.md) or the - [hostname example README](../example-hostname/README.md). - -2. Build the xds hello-world example client. From the `grpc-java/examples/examples-xds` directory: +Build the XDS hello-world example client & server. From the `grpc-java/examples/examples-xds` +directory: ``` $ ../gradlew installDist ``` -This creates the script `build/install/example-xds/bin/xds-hello-world-client` -that runs the example. +This creates the scripts `build/install/example-xds/bin/hello-world-client-xds` and +`build/install/example-xds/bin/hello-world-server-xds`. -To start the server, run: +### Run the example without using XDS Credentials -``` -$ ../build/install/hostname/bin/hello-world-server -$ # or -$ ../example-hostname/build/install/hostname/bin/hostname-server -``` - -And in a different terminal window run this client: - -``` -$ ./build/install/example-xds/bin/xds-hello-world-client -``` - -However, that didn't use XDS! To use XDS we assume you have deployed the server -in your deployment environment and know its name. You need to set the -`GRPC_XDS_BOOTSTRAP` environment variable to point to a gRPC XDS bootstrap -file (see [gRFC A27](https://github.com/grpc/proposal/pull/170) for the -bootstrap format). Then use the `xds:` target scheme during -channel creation. +To use XDS, you should first deploy the XDS management server in your deployment environment +and know its name. You need to set the `GRPC_XDS_BOOTSTRAP` environment variable to point to the +gRPC XDS bootstrap file (see +[gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#xdsclient-and-bootstrap-file) for the +bootstrap format). This is needed by both `build/install/example-xds/bin/hello-world-client-xds` +and `build/install/example-xds/bin/hello-world-server-xds`. +1. To start the XDS-enabled example server, run: ``` $ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json -$ ./build/install/example-xds/bin/xds-hello-world-client "XDS world" xds:///yourServersName +$ ./build/install/example-xds/bin/hello-world-server-xds 8000 my-test-xds-server ``` + +The first command line argument is the port to listen on (`8000`) and the second argument is a string +id (`my-test-xds-server`) to be included in the greeting response to the client. + +2. In a different terminal window, run the XDS-enabled example client: +``` +$ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json +$ ./build/install/example-xds/bin/xds-hello-world-client xds:///yourServersName:8000 my-test-xds-client +``` +The first command line argument (`xds:///yourServersName:8000`) is the target to connect to using the +`xds:` target scheme and the second argument (`my-test-xds-client`) is the name you wish to include in +the greeting request to the server. + +### Run the example with xDS Credentials + +The above example used plaintext (insecure) credentials as explicitly provided by the client and server +code. We will now demonstrate how the code can authorize use of xDS provided credentials by using +`XdsChannelCredentials` on the client side and using `XdsServerBuilder.useXdsSecurityWithPlaintextFallback()` +on the server side. This code is enabled by providing an additional command line argument. + +1. On the server side, add `--secure` on the command line to authorize use of xDS security: +``` +$ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json +$ ./build/install/example-xds/bin/hello-world-server-xds 8000 my-test-xds-server --secure +``` + +2. Similarly, add `--secure` on the comamnd line when you run the xDS client: +``` +$ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json +$ ./build/install/example-xds/bin/hello-world-client-xds xds:///yourServersName:8000 my-test-xds-client --secure +``` + +In this case, if the xDS management server is configured to provide mTLS credentials (for example) to the client and +server, then they will use these credentials to create an mTLS channel to authenticate and encrypt. diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 3f20bd2b14..232cc185c1 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -1,5 +1,9 @@ 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.13' + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' id 'java' } @@ -19,25 +23,46 @@ targetCompatibility = 1.7 // 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.34.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def nettyTcNativeVersion = '2.0.31.Final' +def protocVersion = '3.12.0' dependencies { - // This example's client is the same as the helloworld client. We depend on the helloworld - // client's code here - implementation ':examples' - // The only change necessary is an extra runtime dependency on io.grpc:grpc-xds - runtimeOnly "io.grpc:grpc-xds:${grpcVersion}" + implementation "io.grpc:grpc-netty:${grpcVersion}" + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-xds:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + runtimeOnly "io.netty:netty-tcnative-boringssl-static:${nettyTcNativeVersion}" +} + +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 helloWorldClient(type: CreateStartScripts) { - mainClassName = 'io.grpc.examples.helloworld.HelloWorldClient' - applicationName = 'xds-hello-world-client' +task helloWorldClientXds(type: CreateStartScripts) { + mainClassName = 'io.grpc.examples.helloworldxds.HelloWorldClientXds' + applicationName = 'hello-world-client-xds' + outputDir = new File(project.buildDir, 'tmp') + classpath = startScripts.classpath +} + +task helloWorldServerXds(type: CreateStartScripts) { + mainClassName = 'io.grpc.examples.helloworldxds.HelloWorldServerXds' + applicationName = 'hello-world-server-xds' outputDir = new File(project.buildDir, 'tmp') classpath = startScripts.classpath } applicationDistribution.into('bin') { - from(helloWorldClient) + from(helloWorldClientXds) + from(helloWorldServerXds) fileMode = 0755 } diff --git a/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldClientXds.java b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldClientXds.java new file mode 100644 index 0000000000..d4d560e83a --- /dev/null +++ b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldClientXds.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020 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.helloworldxds; + +import io.grpc.ChannelCredentials; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +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.netty.GrpcSslContexts; +import io.grpc.netty.NettyChannelBuilder; +import io.grpc.xds.XdsChannelCredentials; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import java.io.File; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.net.ssl.SSLException; + +/** + * A simple xDS client that requests a greeting from the {@link HelloWorldServerXds}. + */ +public class HelloWorldClientXds { + private static final Logger logger = Logger.getLogger(HelloWorldClientXds.class.getName()); + private final ManagedChannel channel; + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client connecting to HelloWorld server at {@code host:port}. */ + public HelloWorldClientXds(String target, boolean useXdsCreds) throws SSLException { + this.channel = + Grpc.newChannelBuilder( + target, + useXdsCreds + ? XdsChannelCredentials.create(InsecureChannelCredentials.create()) + : InsecureChannelCredentials.create()) + .build(); + blockingStub = GreeterGrpc.newBlockingStub(this.channel); + } + + public void shutdown() throws InterruptedException { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + } + + /** 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 second element of {@code args} is the name to use in the + * greeting. + */ + public static void main(String[] args) throws Exception { + String user = "xds-client"; + boolean useXdsCreds = false; + if (args.length < 1 || args.length > 3) { + System.out.println("USAGE: HelloWorldClientXds target [name [--secure]]\n"); + System.err.println(" target The xds target to connect to using the 'xds:' target scheme."); + System.err.println(" name The name you wish to include in the greeting request. Defaults to " + user); + System.err.println( + " '--secure' Indicates using xDS credentials otherwise defaults to insecure."); + System.exit(1); + } + if (args.length > 1) { + user = args[1]; + if (args.length == 3) { + useXdsCreds = args[2].toLowerCase().startsWith("--s"); + } + } + HelloWorldClientXds client = new HelloWorldClientXds(args[0], useXdsCreds); + try { + client.greet(user); + } finally { + client.shutdown(); + } + } +} diff --git a/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldServerXds.java b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldServerXds.java new file mode 100644 index 0000000000..2d3b027c48 --- /dev/null +++ b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HelloWorldServerXds.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020 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.helloworldxds; + +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 io.grpc.xds.internal.sds.XdsServerBuilder; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An xDS-managed Server for the {@code Greeter} service. + */ +public class HelloWorldServerXds { + private static final Logger logger = Logger.getLogger(HelloWorldServerXds.class.getName()); + private final int port; + private final boolean useXdsCreds; + private final String hostName; + private Server server; + + public HelloWorldServerXds(int port, String hostName, boolean useXdsCreds) { + this.port = port; + this.hostName = hostName; + this.useXdsCreds = useXdsCreds; + } + + private void start() throws IOException { + XdsServerBuilder builder = XdsServerBuilder.forPort(port).addService(new HostnameGreeter(hostName)); + if (useXdsCreds) { + builder = builder.useXdsSecurityWithPlaintextFallback(); + } + server = builder.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 { + HelloWorldServerXds.this.stop(); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "During stop", e); + } + 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 { + boolean useXdsCreds = false; + String hostName = null; + if (args.length < 1 || args.length > 3) { + System.out.println("USAGE: HelloWorldServerTls port [hostname [--secure]]"); + System.err.println(""); + System.err.println(" port The port to bind to."); + System.err.println(" hostname The name clients will see in greet responses. "); + System.err.println(" Defaults to the machine's hostname"); + System.out.println( + " '--secure' Indicates using xDS credentials options; otherwise defaults to insecure credentials."); + System.exit(1); + } + if (args.length > 1) { + hostName = args[1]; + if (args.length == 3) { + useXdsCreds = args[2].toLowerCase().startsWith("--s"); + } + } + final HelloWorldServerXds server = + new HelloWorldServerXds(Integer.parseInt(args[0]), hostName, useXdsCreds); + server.start(); + server.blockUntilShutdown(); + } +} diff --git a/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HostnameGreeter.java b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HostnameGreeter.java new file mode 100644 index 0000000000..5595e47b9c --- /dev/null +++ b/examples/example-xds/src/main/java/io/grpc/examples/helloworldxds/HostnameGreeter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 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.helloworldxds; + +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.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 final String serverName; + + public HostnameGreeter(String serverName) { + if (serverName == null || serverName.isEmpty()) { + serverName = determineHostname(); + } + this.serverName = serverName; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + req.getName() + ", from " + serverName) + .build(); + 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-xds/src/main/proto/helloworld/helloworld.proto b/examples/example-xds/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 0000000000..be25469737 --- /dev/null +++ b/examples/example-xds/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2020 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; +}