Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dualstack example #11451

Merged
merged 7 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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() }
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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 < 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());
}
}
125 changes: 125 additions & 0 deletions examples/src/main/java/io/grpc/examples/dualstack/DualStackServer.java
Original file line number Diff line number Diff line change
@@ -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.examples.loadbalance.LoadBalanceServer;
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
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<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 {
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(() -> {
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<HelloReply> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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 io.grpc.examples.loadbalance.LoadBalanceServer;
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 {

// This is a fake name resolver, so we just hard code the address here.
private static final ImmutableMap<String, List<List<SocketAddress>>> addrStore =
ImmutableMap.<String, List<List<SocketAddress>>>builder()
.put(exampleServiceName,
Arrays.stream(LoadBalanceServer.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<SocketAddress> 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.
ejona86 marked this conversation as resolved.
Show resolved Hide resolved
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<List<SocketAddress>> addresses = addrStore.get(uri.getPath().substring(1));
try {
List<EquivalentAddressGroup> eagList = new ArrayList<>();
for (List<SocketAddress> 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));
}
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading