xds: create a full xDS example with XDS Channel creds and server xDS options (#7535)

This commit is contained in:
sanjaypujare 2020-10-23 08:38:27 -07:00 committed by GitHub
parent 26a4ca38ec
commit b6601ba273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 394 additions and 39 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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<HelloReply> 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();
}
}

View File

@ -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;
}