From d5ca826e5c316a2291d1aef099de48005c36105b Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Fri, 2 Aug 2024 17:16:24 -0700 Subject: [PATCH 1/7] checkpoint --- .../examples/dualstack/DualStackClient.java | 86 +++++++++++ .../examples/dualstack/DualStackServer.java | 94 ++++++++++++ .../ExampleDualStackNameResolver.java | 141 ++++++++++++++++++ .../ExampleDualStackNameResolverProvider.java | 47 ++++++ 4 files changed, 368 insertions(+) create mode 100644 examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java create mode 100644 examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java create mode 100644 examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java create mode 100644 examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java new file mode 100644 index 00000000000..a9687a0adc3 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.*; +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; + +public class DualStackClient { + public static final String exampleScheme = "example"; + public static final String exampleServiceName = "lb.example.grpc.io"; + private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + public DualStackClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + public static void main(String[] args) throws Exception { + NameResolverRegistry.getDefaultRegistry() + .register(new ExampleDualStackNameResolverProvider()); + + logger.info("Use default DNS resolver"); + ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + logger.info("Change to use example name resolver"); + /* + Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection + "resolver.example.grpc.io" is converted to {@link java.net.URI.path} + */ + channel = ManagedChannelBuilder.forTarget( + String.format("%s:///%s", exampleScheme, exampleServiceName)) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + public void greet(String 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()); + } +} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java new file mode 100644 index 00000000000..a846c5b066a --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +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 DualStackServer { + static public final int serverCount = 3; + static public final int startPort = 50051; + private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); + private Server[] servers; + + public static void main(String[] args) throws IOException, InterruptedException { + final DualStackServer server = new DualStackServer(); + server.start(); + server.blockUntilShutdown(); + } + + private void start() throws IOException { + servers = new Server[serverCount]; + for (int i = 0; i < serverCount; i++) { + int port = startPort + i; + servers[i] = ServerBuilder.forPort(port) + .addService(new GreeterImpl(port)) + .build() + .start(); + logger.info("Server started, listening on " + port); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DualStackServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + })); + } + + private void stop() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + } + + private void blockUntilShutdown() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].awaitTermination(); + } + } + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + int port; + + public GreeterImpl(int port) { + this.port = port; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java new file mode 100644 index 00000000000..a4158a92540 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -0,0 +1,141 @@ +/* + * Copyright 2024 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.dualstack; + +import com.google.common.collect.ImmutableMap; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; + +import io.grpc.examples.loadbalance.LoadBalanceServer; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; + +public class ExampleDualStackNameResolver extends NameResolver { + + private Listener2 listener; + + private final URI uri; + + private final Map>> addrStore; + + public ExampleDualStackNameResolver(URI targetUri) { + this.uri = targetUri; + // This is a fake name resolver, so we just hard code the address here. + addrStore = ImmutableMap.>>builder() + .put(exampleServiceName, + Stream.iterate(LoadBalanceServer.startPort, p->p+1) + .limit(LoadBalanceServer.serverCount) + .map(port-> getLocalAddrs(port)) + .collect(Collectors.toList()) + ) + .build(); + } + + private static List getLocalAddrs(Integer port) { + return Arrays.asList( + new InetSocketAddress("127.0.0.1", port), + new InetSocketAddress("::1", port)); + } + + @Override + public String getServiceAuthority() { + // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. + if (uri.getHost() != null) { + return uri.getHost(); + } + return "no host"; + } + + @Override + public void shutdown() { + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + this.resolve(); + } + + @Override + public void refresh() { + this.resolve(); + } + + private void resolve() { + List> addresses = addrStore.get(uri.getPath().substring(1)); + try { + List eagList = new ArrayList<>(); + for (List endpoint : addresses) { + // convert to socket address + List socketAddresses = endpoint.stream() + .map(this::toSocketAddress) + .collect(Collectors.toList()); + // every server is an EquivalentAddressGroup, so they can be accessed randomly + eagList.add(addrToEquivalentAddressGroup(socketAddresses)); + } + ResolutionResult resolutionResult = ResolutionResult.newBuilder() + .setAddresses(eagList) + .build(); + + this.listener.onResult(resolutionResult); + } catch (Exception e){ + // when error occurs, notify listener + this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); + } + } + + private SocketAddress toSocketAddress(InetSocketAddress address) { + return new InetSocketAddress(address.getHostName(), address.getPort()); + } + + static List getV6Addresses(int port) throws UnknownHostException { + List v6addresses = new ArrayList<>(); + InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + for (InetAddress address : addresses) { + if (address.getAddress().length != 4) { + v6addresses.add(new java.net.InetSocketAddress(address, port)); + } + } + return v6addresses; + } + + static SocketAddress getV4Address(int port) throws UnknownHostException { + InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + for (InetAddress address : addresses) { + if (address.getAddress().length == 4) { + return new java.net.InetSocketAddress(address, port); + } + } + return null; // means it is v6 only + } + + private EquivalentAddressGroup addrToEquivalentAddressGroup(List addrList) { + return new EquivalentAddressGroup(addrList); + } +} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java new file mode 100644 index 00000000000..6cf22f170c2 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; + +import java.net.URI; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleScheme; + +public class ExampleDualStackNameResolverProvider extends NameResolverProvider { + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + return new ExampleDualStackNameResolver(targetUri); + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; + } + + @Override + // gRPC choose the first NameResolverProvider that supports the target URI scheme. + public String getDefaultScheme() { + return exampleScheme; + } +} From 5747970027df3871583f18e02636601c92c509e9 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Mon, 5 Aug 2024 18:11:58 -0700 Subject: [PATCH 2/7] Dualstack example --- examples/build.gradle | 9 ++- .../examples/dualstack/DualStackClient.java | 23 +++--- .../examples/dualstack/DualStackServer.java | 69 ++++++++++++----- .../ExampleDualStackNameResolver.java | 75 +++++-------------- .../loadbalance/ExampleNameResolver.java | 12 ++- .../loadbalance/LoadBalanceServer.java | 32 ++++---- .../nameresolve/NameResolveClient.java | 12 ++- 7 files changed, 111 insertions(+), 121 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 076e0c4a25b..8014dfbdf46 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -31,13 +31,12 @@ 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" // examples/advanced need this for JsonFormat implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" - runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" - testImplementation "io.grpc:grpc-testing:${grpcVersion}" testImplementation "io.grpc:grpc-inprocess:${grpcVersion}" testImplementation "junit:junit:4.13.2" @@ -71,8 +70,8 @@ startScripts.enabled = false // the class io.grpc.examples.helloworld.HelloWorldClient, it creates the task // helloWorldClient with script name hello-world-client. def createStartScripts(String mainClassName) { - String bareName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1); - String taskName = bareName.uncapitalize(); + String bareName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1) + String taskName = bareName.uncapitalize() def newTask = tasks.register(taskName, CreateStartScripts) { mainClass = mainClassName applicationName = taskName.replaceAll('([A-Z])') { '-' + it[0].uncapitalize() } @@ -96,6 +95,8 @@ createStartScripts('io.grpc.examples.cancellation.CancellationServer') createStartScripts('io.grpc.examples.customloadbalance.CustomLoadBalanceClient') createStartScripts('io.grpc.examples.deadline.DeadlineClient') createStartScripts('io.grpc.examples.deadline.DeadlineServer') +createStartScripts('io.grpc.examples.dualstack.DualStackClient') +createStartScripts('io.grpc.examples.dualstack.DualStackServer') createStartScripts('io.grpc.examples.errordetails.ErrorDetailsExample') createStartScripts('io.grpc.examples.experimental.CompressingHelloWorldClient') createStartScripts('io.grpc.examples.grpcproxy.GrpcProxy') diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java index a9687a0adc3..56251518329 100644 --- a/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java +++ b/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java @@ -16,18 +16,20 @@ package io.grpc.examples.dualstack; -import io.grpc.*; +import io.grpc.Channel; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.NameResolverRegistry; +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; public class DualStackClient { - public static final String exampleScheme = "example"; - public static final String exampleServiceName = "lb.example.grpc.io"; + public static final String channelTarget = "example:///lb.example.grpc.io"; private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); private final GreeterGrpc.GreeterBlockingStub blockingStub; @@ -39,7 +41,7 @@ public static void main(String[] args) throws Exception { NameResolverRegistry.getDefaultRegistry() .register(new ExampleDualStackNameResolverProvider()); - logger.info("Use default DNS resolver"); + logger.info("\n **** Use default DNS resolver ****"); ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") .usePlaintext() .build(); @@ -52,16 +54,15 @@ public static void main(String[] args) throws Exception { channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } - logger.info("Change to use example name resolver"); + logger.info("\n **** Change to use example name resolver ****"); /* Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection "resolver.example.grpc.io" is converted to {@link java.net.URI.path} */ - channel = ManagedChannelBuilder.forTarget( - String.format("%s:///%s", exampleScheme, exampleServiceName)) - .defaultLoadBalancingPolicy("round_robin") - .usePlaintext() - .build(); + channel = ManagedChannelBuilder.forTarget(channelTarget) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); try { DualStackClient client = new DualStackClient(channel); for (int i = 0; i < 5; i++) { diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java index a846c5b066a..3742cfb6611 100644 --- a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java +++ b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -21,17 +21,19 @@ import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.loadbalance.LoadBalanceServer; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.stub.StreamObserver; - import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; public class DualStackServer { - static public final int serverCount = 3; - static public final int startPort = 50051; private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); - private Server[] servers; + private List servers; public static void main(String[] args) throws IOException, InterruptedException { final DualStackServer server = new DualStackServer(); @@ -40,13 +42,38 @@ public static void main(String[] args) throws IOException, InterruptedException } private void start() throws IOException { - servers = new Server[serverCount]; - for (int i = 0; i < serverCount; i++) { - int port = startPort + i; - servers[i] = ServerBuilder.forPort(port) - .addService(new GreeterImpl(port)) - .build() - .start(); + InetSocketAddress inetSocketAddress; + + servers = new ArrayList<>(); + for (int i = 0; i < LoadBalanceServer.SERVER_PORTS.length; i++ ) { + String addressType; + int port = LoadBalanceServer.SERVER_PORTS[i]; + ServerBuilder serverBuilder; + switch (i) { + case 0: + serverBuilder = ServerBuilder.forPort(port); // bind to both IPv4 and IPv6 + addressType = "both IPv4 and IPv6"; + break; + case 1: + // bind to IPv4 only + inetSocketAddress = new InetSocketAddress("127.0.0.1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv4 only"; + break; + case 2: + // bind to IPv6 only + inetSocketAddress = new InetSocketAddress("::1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv6 only"; + break; + default: + throw new IllegalStateException("Unexpected value: " + i); + } + + servers.add(serverBuilder + .addService(new GreeterImpl(port, addressType)) + .build() + .start()); logger.info("Server started, listening on " + port); } Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -61,17 +88,17 @@ private void start() throws IOException { } private void stop() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); + for (Server server : servers) { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } } private void blockUntilShutdown() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].awaitTermination(); + for (Server server : servers) { + if (server != null) { + server.awaitTermination(); } } } @@ -79,14 +106,18 @@ private void blockUntilShutdown() throws InterruptedException { static class GreeterImpl extends GreeterGrpc.GreeterImplBase { int port; + String addressType; - public GreeterImpl(int port) { + public GreeterImpl(int port, String addressType) { this.port = port; + this.addressType = addressType; } @Override public void sayHello(HelloRequest req, StreamObserver responseObserver) { - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + String msg = String.format("Hello %s from server<%d> type: %s", + req.getName(), this.port, addressType); + HelloReply reply = HelloReply.newBuilder().setMessage(msg).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java index a4158a92540..68be032c01d 100644 --- a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java +++ b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -16,48 +16,42 @@ package io.grpc.examples.dualstack; +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; + import com.google.common.collect.ImmutableMap; import io.grpc.EquivalentAddressGroup; import io.grpc.NameResolver; import io.grpc.Status; - import io.grpc.examples.loadbalance.LoadBalanceServer; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; public class ExampleDualStackNameResolver extends NameResolver { + // This is a fake name resolver, so we just hard code the address here. + private static final ImmutableMap>> addrStore = + ImmutableMap.>>builder() + .put(exampleServiceName, + Arrays.stream(LoadBalanceServer.SERVER_PORTS) + .mapToObj(port -> getLocalAddrs(port)) + .collect(Collectors.toList()) + ) + .build(); + private Listener2 listener; private final URI uri; - private final Map>> addrStore; - public ExampleDualStackNameResolver(URI targetUri) { this.uri = targetUri; - // This is a fake name resolver, so we just hard code the address here. - addrStore = ImmutableMap.>>builder() - .put(exampleServiceName, - Stream.iterate(LoadBalanceServer.startPort, p->p+1) - .limit(LoadBalanceServer.serverCount) - .map(port-> getLocalAddrs(port)) - .collect(Collectors.toList()) - ) - .build(); } - private static List getLocalAddrs(Integer port) { + private static List getLocalAddrs(int port) { return Arrays.asList( new InetSocketAddress("127.0.0.1", port), new InetSocketAddress("::1", port)); @@ -88,54 +82,19 @@ public void refresh() { } private void resolve() { - List> addresses = addrStore.get(uri.getPath().substring(1)); + List> addresses = addrStore.get(uri.getPath().substring(1)); try { List eagList = new ArrayList<>(); - for (List endpoint : addresses) { - // convert to socket address - List socketAddresses = endpoint.stream() - .map(this::toSocketAddress) - .collect(Collectors.toList()); + for (List endpoint : addresses) { // every server is an EquivalentAddressGroup, so they can be accessed randomly - eagList.add(addrToEquivalentAddressGroup(socketAddresses)); + eagList.add(new EquivalentAddressGroup(endpoint)); } - ResolutionResult resolutionResult = ResolutionResult.newBuilder() - .setAddresses(eagList) - .build(); - this.listener.onResult(resolutionResult); + this.listener.onResult(ResolutionResult.newBuilder().setAddresses(eagList).build()); } catch (Exception e){ // when error occurs, notify listener this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); } } - private SocketAddress toSocketAddress(InetSocketAddress address) { - return new InetSocketAddress(address.getHostName(), address.getPort()); - } - - static List getV6Addresses(int port) throws UnknownHostException { - List v6addresses = new ArrayList<>(); - InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); - for (InetAddress address : addresses) { - if (address.getAddress().length != 4) { - v6addresses.add(new java.net.InetSocketAddress(address, port)); - } - } - return v6addresses; - } - - static SocketAddress getV4Address(int port) throws UnknownHostException { - InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); - for (InetAddress address : addresses) { - if (address.getAddress().length == 4) { - return new java.net.InetSocketAddress(address, port); - } - } - return null; // means it is v6 only - } - - private EquivalentAddressGroup addrToEquivalentAddressGroup(List addrList) { - return new EquivalentAddressGroup(addrList); - } } diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java index f562f0ac107..a9e44a9e4c1 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; @@ -44,12 +43,11 @@ public ExampleNameResolver(URI targetUri) { this.uri = targetUri; // This is a fake name resolver, so we just hard code the address here. addrStore = ImmutableMap.>builder() - .put(exampleServiceName, - Stream.iterate(LoadBalanceServer.startPort,p->p+1) - .limit(LoadBalanceServer.serverCount) - .map(port->new InetSocketAddress("localhost",port)) - .collect(Collectors.toList()) - ) + .put(exampleServiceName, + Arrays.stream(LoadBalanceServer.SERVER_PORTS) + .mapToObj(port->new InetSocketAddress("localhost",port)) + .collect(Collectors.toList()) + ) .build(); } diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java index c97d209497a..7a420ba1cf2 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java @@ -24,23 +24,24 @@ import io.grpc.stub.StreamObserver; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; public class LoadBalanceServer { private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName()); - static public final int serverCount = 3; - static public final int startPort = 50051; - private Server[] servers; + static public final int[] SERVER_PORTS = {50051, 50052, 50053}; + private List servers; private void start() throws IOException { - servers = new Server[serverCount]; - for (int i = 0; i < serverCount; i++) { - int port = startPort + i; - servers[i] = ServerBuilder.forPort(port) + servers = new ArrayList<>(); + for (int port :SERVER_PORTS) { + servers.add( + ServerBuilder.forPort(port) .addService(new GreeterImpl(port)) .build() - .start(); + .start()); logger.info("Server started, listening on " + port); } Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -55,17 +56,17 @@ private void start() throws IOException { } private void stop() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); + for (Server server : servers) { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } } private void blockUntilShutdown() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].awaitTermination(); + for (Server server : servers) { + if (server != null) { + server.awaitTermination(); } } } @@ -86,7 +87,8 @@ public GreeterImpl(int port) { @Override public void sayHello(HelloRequest req, StreamObserver responseObserver) { - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java index ac6fdd32549..9aaccbe1096 100644 --- a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java +++ b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java @@ -26,8 +26,7 @@ import java.util.logging.Logger; public class NameResolveClient { - public static final String exampleScheme = "example"; - public static final String exampleServiceName = "lb.example.grpc.io"; + public static final String channelTarget = "example:///lb.example.grpc.io"; private static final Logger logger = Logger.getLogger(NameResolveClient.class.getName()); private final GreeterGrpc.GreeterBlockingStub blockingStub; @@ -56,11 +55,10 @@ public static void main(String[] args) throws Exception { Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection "resolver.example.grpc.io" is converted to {@link java.net.URI.path} */ - channel = ManagedChannelBuilder.forTarget( - String.format("%s:///%s", exampleScheme, exampleServiceName)) - .defaultLoadBalancingPolicy("round_robin") - .usePlaintext() - .build(); + channel = ManagedChannelBuilder.forTarget(channelTarget) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); try { NameResolveClient client = new NameResolveClient(channel); for (int i = 0; i < 5; i++) { From 455a39e2275d5b0e41392a731bd1464bbf916223 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 7 Aug 2024 16:13:05 -0700 Subject: [PATCH 3/7] separated example-dualstack into own project --- examples/build.gradle | 9 +- examples/example-dualstack/README.md | 54 ++++++++ examples/example-dualstack/build.gradle | 82 ++++++++++++ examples/example-dualstack/pom.xml | 118 +++++++++++++++++ examples/example-dualstack/settings.gradle | 1 + .../examples/dualstack/DualStackClient.java | 87 ++++++++++++ .../examples/dualstack/DualStackServer.java | 125 ++++++++++++++++++ .../ExampleDualStackNameResolver.java | 98 ++++++++++++++ .../ExampleDualStackNameResolverProvider.java | 47 +++++++ .../main/proto/helloworld/helloworld.proto | 37 ++++++ .../examples/dualstack/DualStackServer.java | 6 +- .../ExampleDualStackNameResolver.java | 4 +- .../loadbalance/ExampleNameResolver.java | 3 +- .../loadbalance/LoadBalanceServer.java | 12 +- 14 files changed, 664 insertions(+), 19 deletions(-) create mode 100644 examples/example-dualstack/README.md create mode 100644 examples/example-dualstack/build.gradle create mode 100644 examples/example-dualstack/pom.xml create mode 100644 examples/example-dualstack/settings.gradle create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java create mode 100644 examples/example-dualstack/src/main/proto/helloworld/helloworld.proto diff --git a/examples/build.gradle b/examples/build.gradle index 8014dfbdf46..076e0c4a25b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -31,12 +31,13 @@ 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" // examples/advanced need this for JsonFormat implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" + runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" + testImplementation "io.grpc:grpc-testing:${grpcVersion}" testImplementation "io.grpc:grpc-inprocess:${grpcVersion}" testImplementation "junit:junit:4.13.2" @@ -70,8 +71,8 @@ startScripts.enabled = false // the class io.grpc.examples.helloworld.HelloWorldClient, it creates the task // helloWorldClient with script name hello-world-client. def createStartScripts(String mainClassName) { - String bareName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1) - String taskName = bareName.uncapitalize() + String bareName = mainClassName.substring(mainClassName.lastIndexOf('.') + 1); + String taskName = bareName.uncapitalize(); def newTask = tasks.register(taskName, CreateStartScripts) { mainClass = mainClassName applicationName = taskName.replaceAll('([A-Z])') { '-' + it[0].uncapitalize() } @@ -95,8 +96,6 @@ createStartScripts('io.grpc.examples.cancellation.CancellationServer') createStartScripts('io.grpc.examples.customloadbalance.CustomLoadBalanceClient') createStartScripts('io.grpc.examples.deadline.DeadlineClient') createStartScripts('io.grpc.examples.deadline.DeadlineServer') -createStartScripts('io.grpc.examples.dualstack.DualStackClient') -createStartScripts('io.grpc.examples.dualstack.DualStackServer') createStartScripts('io.grpc.examples.errordetails.ErrorDetailsExample') createStartScripts('io.grpc.examples.experimental.CompressingHelloWorldClient') createStartScripts('io.grpc.examples.grpcproxy.GrpcProxy') diff --git a/examples/example-dualstack/README.md b/examples/example-dualstack/README.md new file mode 100644 index 00000000000..6c191661d1b --- /dev/null +++ b/examples/example-dualstack/README.md @@ -0,0 +1,54 @@ +# gRPC Dualstack Example + +The dualstack example uses a custom name resolver that provides both IPv4 and IPv6 localhost +endpoints for each of 3 server instances. The client will first use the default name resolver and +load balancers which will only connect tot he first server. It will then use the +custom name resolver with round robin to connect to each of the servers in turn. The 3 instances +of the server will bind respectively to: both IPv4 and IPv6, IPv4 only, and IPv6 only. + +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 + +To build the dualstack example server and client. From the + `grpc-java/examples/example-dualstack` directory run: + +```bash +$ ../gradlew installDist +``` + +This creates the scripts +`build/install/example-dualstack/bin/dual-stack-server` + and `build/install/example-dualstack/bin/dual-stack-client`. + +To run the dualstack example, run the server with: + +```bash +$ ./build/install/example-dualstack/bin/dual-stack-server +``` + +And in a different terminal window run the client. + +```bash +$ ./build/install/example-dualstack/bin/dual-stack-client +``` + +### Maven + +If you prefer to use Maven: + +Run in the example-debug directory: + +```bash +$ mvn verify +$ # Run the server in one terminal +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackServer +``` + +```bash +$ # In another terminal run the client +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackClient +``` + diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle new file mode 100644 index 00000000000..f87558b7986 --- /dev/null +++ b/examples/example-dualstack/build.gradle @@ -0,0 +1,82 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + id 'java' + + id "com.google.protobuf" version "0.9.4" + + // 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() +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_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.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protobufVersion = '3.25.3' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-netty:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + + 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 DualStackClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.dualstack.DualStackClient' + applicationName = 'dual-stack-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task DualStackServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.dualstack.DualStackServer' + applicationName = 'dual-stack-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +application { + applicationDistribution.into('bin') { + from(DualStackClient) + from(DualStackServer) + filePermissions { + unix(0755) + } + } +} diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml new file mode 100644 index 00000000000..df5542e4532 --- /dev/null +++ b/examples/example-dualstack/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + io.grpc + example-dualstack + jar + + 1.67.0-SNAPSHOT + example-dualstack + https://github.com/grpc/grpc-java + + + UTF-8 + 1.67.0-SNAPSHOT + 3.25.3 + + 1.8 + 1.8 + + + + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + + + + io.grpc + grpc-services + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + 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-dualstack/settings.gradle b/examples/example-dualstack/settings.gradle new file mode 100644 index 00000000000..2955e70d8b7 --- /dev/null +++ b/examples/example-dualstack/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-dualstack' diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java new file mode 100644 index 00000000000..e54f6ca7d9e --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.Channel; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.NameResolverRegistry; +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; + +public class DualStackClient { + public static final String channelTarget = "example:///lb.example.grpc.io"; + private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + public DualStackClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + public static void main(String[] args) throws Exception { + NameResolverRegistry.getDefaultRegistry() + .register(new ExampleDualStackNameResolverProvider()); + + logger.info("\n **** Use default DNS resolver ****"); + ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request:" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + logger.info("\n **** Change to use example name resolver ****"); + /* + Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection + "resolver.example.grpc.io" is converted to {@link java.net.URI.path} + */ + channel = ManagedChannelBuilder.forTarget(channelTarget) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 10; i++) { + client.greet("request:" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + public void greet(String 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()); + } +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java new file mode 100644 index 00000000000..e9a18b24984 --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class DualStackServer { + private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); + private List servers; + + public static void main(String[] args) throws IOException, InterruptedException { + final DualStackServer server = new DualStackServer(); + server.start(); + server.blockUntilShutdown(); + } + + private void start() throws IOException { + InetSocketAddress inetSocketAddress; + + servers = new ArrayList<>(); + int[] serverPorts = ExampleDualStackNameResolver.SERVER_PORTS; + for (int i = 0; i < serverPorts.length; i++ ) { + String addressType; + int port = serverPorts[i]; + ServerBuilder serverBuilder; + switch (i) { + case 0: + serverBuilder = ServerBuilder.forPort(port); // bind to both IPv4 and IPv6 + addressType = "both IPv4 and IPv6"; + break; + case 1: + // bind to IPv4 only + inetSocketAddress = new InetSocketAddress("127.0.0.1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv4 only"; + break; + case 2: + // bind to IPv6 only + inetSocketAddress = new InetSocketAddress("::1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv6 only"; + break; + default: + throw new IllegalStateException("Unexpected value: " + i); + } + + servers.add(serverBuilder + .addService(new GreeterImpl(port, addressType)) + .build() + .start()); + logger.info("Server started, listening on " + port); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DualStackServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + })); + } + + private void stop() throws InterruptedException { + for (Server server : servers) { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + } + + private void blockUntilShutdown() throws InterruptedException { + for (Server server : servers) { + if (server != null) { + server.awaitTermination(); + } + } + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + int port; + String addressType; + + public GreeterImpl(int port, String addressType) { + this.port = port; + this.addressType = addressType; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + String msg = String.format("Hello %s from server<%d> type: %s", + req.getName(), this.port, addressType); + HelloReply reply = HelloReply.newBuilder().setMessage(msg).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java new file mode 100644 index 00000000000..96981976d1f --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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.dualstack; + +import com.google.common.collect.ImmutableMap; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ExampleDualStackNameResolver extends NameResolver { + static public final int[] SERVER_PORTS = {50051, 50052, 50053}; + + // This is a fake name resolver, so we just hard code the address here. + private static final ImmutableMap>> addrStore = + ImmutableMap.>>builder() + .put("lb.example.grpc.io", + Arrays.stream(SERVER_PORTS) + .mapToObj(port -> getLocalAddrs(port)) + .collect(Collectors.toList()) + ) + .build(); + + private Listener2 listener; + + private final URI uri; + + public ExampleDualStackNameResolver(URI targetUri) { + this.uri = targetUri; + } + + private static List getLocalAddrs(int port) { + return Arrays.asList( + new InetSocketAddress("127.0.0.1", port), + new InetSocketAddress("::1", port)); + } + + @Override + public String getServiceAuthority() { + // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. + if (uri.getHost() != null) { + return uri.getHost(); + } + return "no host"; + } + + @Override + public void shutdown() { + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + this.resolve(); + } + + @Override + public void refresh() { + this.resolve(); + } + + private void resolve() { + List> addresses = addrStore.get(uri.getPath().substring(1)); + try { + List eagList = new ArrayList<>(); + for (List endpoint : addresses) { + // every server is an EquivalentAddressGroup, so they can be accessed randomly + eagList.add(new EquivalentAddressGroup(endpoint)); + } + + this.listener.onResult(ResolutionResult.newBuilder().setAddresses(eagList).build()); + } catch (Exception e){ + // when error occurs, notify listener + this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); + } + } + +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java new file mode 100644 index 00000000000..a01d68aca3e --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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.dualstack; + +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; + +import java.net.URI; + +public class ExampleDualStackNameResolverProvider extends NameResolverProvider { + public static final String exampleScheme = "example"; + + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + return new ExampleDualStackNameResolver(targetUri); + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; + } + + @Override + // gRPC choose the first NameResolverProvider that supports the target URI scheme. + public String getDefaultScheme() { + return exampleScheme; + } +} diff --git a/examples/example-dualstack/src/main/proto/helloworld/helloworld.proto b/examples/example-dualstack/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..c60d9416f1f --- /dev/null +++ b/examples/example-dualstack/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/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java index 3742cfb6611..a04227cb8ae 100644 --- a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java +++ b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -21,7 +21,6 @@ import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; -import io.grpc.examples.loadbalance.LoadBalanceServer; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.stub.StreamObserver; import java.io.IOException; @@ -45,9 +44,10 @@ private void start() throws IOException { InetSocketAddress inetSocketAddress; servers = new ArrayList<>(); - for (int i = 0; i < LoadBalanceServer.SERVER_PORTS.length; i++ ) { + int[] serverPorts = ExampleDualStackNameResolver.SERVER_PORTS; + for (int i = 0; i < serverPorts.length; i++ ) { String addressType; - int port = LoadBalanceServer.SERVER_PORTS[i]; + int port = serverPorts[i]; ServerBuilder serverBuilder; switch (i) { case 0: diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java index 68be032c01d..176bd7fecbc 100644 --- a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java +++ b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -22,7 +22,6 @@ import io.grpc.EquivalentAddressGroup; import io.grpc.NameResolver; import io.grpc.Status; -import io.grpc.examples.loadbalance.LoadBalanceServer; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; @@ -32,12 +31,13 @@ import java.util.stream.Collectors; public class ExampleDualStackNameResolver extends NameResolver { + static public final int[] SERVER_PORTS = {50051, 50052, 50053}; // This is a fake name resolver, so we just hard code the address here. private static final ImmutableMap>> addrStore = ImmutableMap.>>builder() .put(exampleServiceName, - Arrays.stream(LoadBalanceServer.SERVER_PORTS) + Arrays.stream(SERVER_PORTS) .mapToObj(port -> getLocalAddrs(port)) .collect(Collectors.toList()) ) diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java index a9e44a9e4c1..6ef327ade84 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java @@ -33,6 +33,7 @@ public class ExampleNameResolver extends NameResolver { + static private final int[] SERVER_PORTS = {50051, 50052, 50053}; private Listener2 listener; private final URI uri; @@ -44,7 +45,7 @@ public ExampleNameResolver(URI targetUri) { // This is a fake name resolver, so we just hard code the address here. addrStore = ImmutableMap.>builder() .put(exampleServiceName, - Arrays.stream(LoadBalanceServer.SERVER_PORTS) + Arrays.stream(SERVER_PORTS) .mapToObj(port->new InetSocketAddress("localhost",port)) .collect(Collectors.toList()) ) diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java index 7a420ba1cf2..85ae92a537a 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java @@ -31,12 +31,12 @@ public class LoadBalanceServer { private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName()); - static public final int[] SERVER_PORTS = {50051, 50052, 50053}; + static final int[] SERVER_PORTS = {50051, 50052, 50053}; private List servers; private void start() throws IOException { servers = new ArrayList<>(); - for (int port :SERVER_PORTS) { + for (int port : SERVER_PORTS) { servers.add( ServerBuilder.forPort(port) .addService(new GreeterImpl(port)) @@ -57,17 +57,13 @@ private void start() throws IOException { private void stop() throws InterruptedException { for (Server server : servers) { - if (server != null) { - server.shutdown().awaitTermination(30, TimeUnit.SECONDS); - } + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } private void blockUntilShutdown() throws InterruptedException { for (Server server : servers) { - if (server != null) { - server.awaitTermination(); - } + server.awaitTermination(); } } From 0d7980457dd5548d2bec647ab9293118b90f0102 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 7 Aug 2024 16:13:48 -0700 Subject: [PATCH 4/7] cleanup --- .../examples/dualstack/DualStackClient.java | 87 ------------ .../examples/dualstack/DualStackServer.java | 125 ------------------ .../ExampleDualStackNameResolver.java | 100 -------------- .../ExampleDualStackNameResolverProvider.java | 47 ------- 4 files changed, 359 deletions(-) delete mode 100644 examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java delete mode 100644 examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java delete mode 100644 examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java delete mode 100644 examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java deleted file mode 100644 index 56251518329..00000000000 --- a/examples/src/main/java/io/grpc/examples/dualstack/DualStackClient.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024 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.dualstack; - -import io.grpc.Channel; -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; -import io.grpc.NameResolverRegistry; -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; - -public class DualStackClient { - public static final String channelTarget = "example:///lb.example.grpc.io"; - private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); - private final GreeterGrpc.GreeterBlockingStub blockingStub; - - public DualStackClient(Channel channel) { - blockingStub = GreeterGrpc.newBlockingStub(channel); - } - - public static void main(String[] args) throws Exception { - NameResolverRegistry.getDefaultRegistry() - .register(new ExampleDualStackNameResolverProvider()); - - logger.info("\n **** Use default DNS resolver ****"); - ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") - .usePlaintext() - .build(); - try { - DualStackClient client = new DualStackClient(channel); - for (int i = 0; i < 5; i++) { - client.greet("request" + i); - } - } finally { - channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); - } - - logger.info("\n **** Change to use example name resolver ****"); - /* - Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection - "resolver.example.grpc.io" is converted to {@link java.net.URI.path} - */ - channel = ManagedChannelBuilder.forTarget(channelTarget) - .defaultLoadBalancingPolicy("round_robin") - .usePlaintext() - .build(); - try { - DualStackClient client = new DualStackClient(channel); - for (int i = 0; i < 5; i++) { - client.greet("request" + i); - } - } finally { - channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); - } - } - - public void greet(String 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()); - } -} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java deleted file mode 100644 index a04227cb8ae..00000000000 --- a/examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 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.dualstack; - -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpc.examples.helloworld.GreeterGrpc; -import io.grpc.examples.helloworld.HelloReply; -import io.grpc.examples.helloworld.HelloRequest; -import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; -import io.grpc.stub.StreamObserver; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; - -public class DualStackServer { - private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); - private List servers; - - public static void main(String[] args) throws IOException, InterruptedException { - final DualStackServer server = new DualStackServer(); - server.start(); - server.blockUntilShutdown(); - } - - private void start() throws IOException { - InetSocketAddress inetSocketAddress; - - servers = new ArrayList<>(); - int[] serverPorts = ExampleDualStackNameResolver.SERVER_PORTS; - for (int i = 0; i < serverPorts.length; i++ ) { - String addressType; - int port = serverPorts[i]; - ServerBuilder serverBuilder; - switch (i) { - case 0: - serverBuilder = ServerBuilder.forPort(port); // bind to both IPv4 and IPv6 - addressType = "both IPv4 and IPv6"; - break; - case 1: - // bind to IPv4 only - inetSocketAddress = new InetSocketAddress("127.0.0.1", port); - serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); - addressType = "IPv4 only"; - break; - case 2: - // bind to IPv6 only - inetSocketAddress = new InetSocketAddress("::1", port); - serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); - addressType = "IPv6 only"; - break; - default: - throw new IllegalStateException("Unexpected value: " + i); - } - - servers.add(serverBuilder - .addService(new GreeterImpl(port, addressType)) - .build() - .start()); - logger.info("Server started, listening on " + port); - } - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - System.err.println("*** shutting down gRPC server since JVM is shutting down"); - try { - DualStackServer.this.stop(); - } catch (InterruptedException e) { - e.printStackTrace(System.err); - } - System.err.println("*** server shut down"); - })); - } - - private void stop() throws InterruptedException { - for (Server server : servers) { - if (server != null) { - server.shutdown().awaitTermination(30, TimeUnit.SECONDS); - } - } - } - - private void blockUntilShutdown() throws InterruptedException { - for (Server server : servers) { - if (server != null) { - server.awaitTermination(); - } - } - } - - static class GreeterImpl extends GreeterGrpc.GreeterImplBase { - - int port; - String addressType; - - public GreeterImpl(int port, String addressType) { - this.port = port; - this.addressType = addressType; - } - - @Override - public void sayHello(HelloRequest req, StreamObserver responseObserver) { - String msg = String.format("Hello %s from server<%d> type: %s", - req.getName(), this.port, addressType); - HelloReply reply = HelloReply.newBuilder().setMessage(msg).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - } - } -} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java deleted file mode 100644 index 176bd7fecbc..00000000000 --- a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2024 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.dualstack; - -import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; - -import com.google.common.collect.ImmutableMap; -import io.grpc.EquivalentAddressGroup; -import io.grpc.NameResolver; -import io.grpc.Status; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public class ExampleDualStackNameResolver extends NameResolver { - static public final int[] SERVER_PORTS = {50051, 50052, 50053}; - - // This is a fake name resolver, so we just hard code the address here. - private static final ImmutableMap>> addrStore = - ImmutableMap.>>builder() - .put(exampleServiceName, - Arrays.stream(SERVER_PORTS) - .mapToObj(port -> getLocalAddrs(port)) - .collect(Collectors.toList()) - ) - .build(); - - private Listener2 listener; - - private final URI uri; - - public ExampleDualStackNameResolver(URI targetUri) { - this.uri = targetUri; - } - - private static List getLocalAddrs(int port) { - return Arrays.asList( - new InetSocketAddress("127.0.0.1", port), - new InetSocketAddress("::1", port)); - } - - @Override - public String getServiceAuthority() { - // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. - if (uri.getHost() != null) { - return uri.getHost(); - } - return "no host"; - } - - @Override - public void shutdown() { - } - - @Override - public void start(Listener2 listener) { - this.listener = listener; - this.resolve(); - } - - @Override - public void refresh() { - this.resolve(); - } - - private void resolve() { - List> addresses = addrStore.get(uri.getPath().substring(1)); - try { - List eagList = new ArrayList<>(); - for (List endpoint : addresses) { - // every server is an EquivalentAddressGroup, so they can be accessed randomly - eagList.add(new EquivalentAddressGroup(endpoint)); - } - - this.listener.onResult(ResolutionResult.newBuilder().setAddresses(eagList).build()); - } catch (Exception e){ - // when error occurs, notify listener - this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); - } - } - -} diff --git a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java b/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java deleted file mode 100644 index 6cf22f170c2..00000000000 --- a/examples/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2024 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.dualstack; - -import io.grpc.NameResolver; -import io.grpc.NameResolverProvider; - -import java.net.URI; - -import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleScheme; - -public class ExampleDualStackNameResolverProvider extends NameResolverProvider { - @Override - public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { - return new ExampleDualStackNameResolver(targetUri); - } - - @Override - protected boolean isAvailable() { - return true; - } - - @Override - protected int priority() { - return 5; - } - - @Override - // gRPC choose the first NameResolverProvider that supports the target URI scheme. - public String getDefaultScheme() { - return exampleScheme; - } -} From 8acb4c0cb914a635d96d7797248836960e2a8514 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 7 Aug 2024 17:55:38 -0700 Subject: [PATCH 5/7] Added javadoc and eliminated unnecessary null checks. --- .../io/grpc/examples/dualstack/DualStackClient.java | 8 ++++++++ .../io/grpc/examples/dualstack/DualStackServer.java | 13 +++++++------ .../dualstack/ExampleDualStackNameResolver.java | 4 ++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java index e54f6ca7d9e..b9993a524d6 100644 --- a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java @@ -28,6 +28,14 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * A client that requests greetings from the {@link DualStackServer}. + * First it sends 5 requests using the default nameresolver and load balancer. + * Then it sends 10 requests using the example nameresolver and round robin load balancer. These + * requests are evenly distributed among the 3 servers rather than favoring the server listening + * on both addresses because the ExampleDualStackNameResolver groups the 3 servers as 3 endpoints + * each with 2 addresses. + */ public class DualStackClient { public static final String channelTarget = "example:///lb.example.grpc.io"; private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java index e9a18b24984..43b21e963f8 100644 --- a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -30,6 +30,11 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +/** + * Starts 3 different greeter services each on its own port, but all for localhost. + * The first service listens on both IPv4 and IPv6, + * the second on just IPv4, and the third on just IPv6. + */ public class DualStackServer { private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); private List servers; @@ -89,17 +94,13 @@ private void start() throws IOException { private void stop() throws InterruptedException { for (Server server : servers) { - if (server != null) { - server.shutdown().awaitTermination(30, TimeUnit.SECONDS); - } + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } private void blockUntilShutdown() throws InterruptedException { for (Server server : servers) { - if (server != null) { - server.awaitTermination(); - } + server.awaitTermination(); } } diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java index 96981976d1f..bed23412602 100644 --- a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -28,6 +28,10 @@ import java.util.List; import java.util.stream.Collectors; +/** + * A fake name resolver that resolves to a hard-coded list of 3 endpoints (EquivalentAddressGropu) + * each with 2 addresses (one IPv4 and one IPv6). + */ public class ExampleDualStackNameResolver extends NameResolver { static public final int[] SERVER_PORTS = {50051, 50052, 50053}; From 4b65c5c895b96972173be2df7f6b3b347d622c87 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 7 Aug 2024 18:40:09 -0700 Subject: [PATCH 6/7] change getServiceAuthority to use uri.path instead of uri.host. --- .../examples/dualstack/ExampleDualStackNameResolver.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java index bed23412602..70675b3de3d 100644 --- a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -61,11 +61,7 @@ private static List getLocalAddrs(int port) { @Override public String getServiceAuthority() { - // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. - if (uri.getHost() != null) { - return uri.getHost(); - } - return "no host"; + return uri.getPath().substring(1); } @Override From 7a738b0c09733b687cdf3962832ab2785f7e38be Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Thu, 8 Aug 2024 15:13:07 -0700 Subject: [PATCH 7/7] Add grpc-netty dependency to pom and cleanup tests from build.gradle and add settings.gradle file --- examples/example-dualstack/build.gradle | 3 --- examples/example-dualstack/pom.xml | 4 ++++ examples/example-dualstack/settings.gradle | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle index f87558b7986..32b35af8a87 100644 --- a/examples/example-dualstack/build.gradle +++ b/examples/example-dualstack/build.gradle @@ -34,9 +34,6 @@ dependencies { implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" compileOnly "org.apache.tomcat:annotations-api:6.0.53" - - testImplementation 'junit:junit:4.13.2' - testImplementation "io.grpc:grpc-testing:${grpcVersion}" } protobuf { diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index df5542e4532..710b48ee617 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -44,6 +44,10 @@ io.grpc grpc-stub + + io.grpc + grpc-netty + org.apache.tomcat annotations-api diff --git a/examples/example-dualstack/settings.gradle b/examples/example-dualstack/settings.gradle index 2955e70d8b7..0aae8f7304e 100644 --- a/examples/example-dualstack/settings.gradle +++ b/examples/example-dualstack/settings.gradle @@ -1 +1,10 @@ +pluginManagement { + repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" + } + gradlePluginPortal() + } +} + rootProject.name = 'example-dualstack'