mirror of https://github.com/grpc/grpc-java.git
rls: add proto and converter (#6743)
This commit is contained in:
parent
37b231348e
commit
0fd4975d4c
|
|
@ -12,7 +12,7 @@ publishing {
|
||||||
|
|
||||||
pom.withXml {
|
pom.withXml {
|
||||||
// Generate bom using subprojects
|
// Generate bom using subprojects
|
||||||
def internalProjects = [project.name, 'grpc-gae-interop-testing-jdk8', 'grpc-compiler']
|
def internalProjects = [project.name, 'grpc-gae-interop-testing-jdk8', 'grpc-compiler', 'grpc-rls']
|
||||||
|
|
||||||
def dependencyManagement = asNode().appendNode('dependencyManagement')
|
def dependencyManagement = asNode().appendNode('dependencyManagement')
|
||||||
def dependencies = dependencyManagement.appendNode('dependencies')
|
def dependencies = dependencyManagement.appendNode('dependencies')
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ curl -Ls https://github.com/grpc/grpc-proto/archive/master.tar.gz | tar xz -C "$
|
||||||
base="$tmpdir/grpc-proto-master"
|
base="$tmpdir/grpc-proto-master"
|
||||||
|
|
||||||
# Copy protos in 'src/main/proto' from grpc-proto for these projects
|
# Copy protos in 'src/main/proto' from grpc-proto for these projects
|
||||||
for project in alts grpclb services; do
|
for project in alts grpclb services rls; do
|
||||||
while read -r proto; do
|
while read -r proto; do
|
||||||
[ -f "$base/$proto" ] && cp "$base/$proto" "$project/src/main/proto/$proto"
|
[ -f "$base/$proto" ] && cp "$base/$proto" "$project/src/main/proto/$proto"
|
||||||
echo "$proto"
|
echo "$proto"
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,24 @@ public class JsonUtil {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a number from an object for the given key, casted to an long. If the key is not
|
||||||
|
* present, this returns null. If the value is not a Double or loses precision when cast to an
|
||||||
|
* long, throws an exception.
|
||||||
|
*/
|
||||||
|
public static Long getNumberAsLong(Map<String, ?> obj, String key) {
|
||||||
|
Double d = getNumber(obj, key);
|
||||||
|
if (d == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long l = d.longValue();
|
||||||
|
if (l != d) {
|
||||||
|
throw new ClassCastException("Number expected to be long: " + d);
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a string from an object for the given key. If the key is not present, this returns null.
|
* Gets a string from an object for the given key. If the key is not present, this returns null.
|
||||||
* If the value is not a String, throws an exception.
|
* If the value is not a String, throws an exception.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
plugins {
|
||||||
|
id "java"
|
||||||
|
id "maven-publish"
|
||||||
|
id "com.google.protobuf"
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "gRPC: RouteLookupService Loadbalancing plugin"
|
||||||
|
|
||||||
|
evaluationDependsOn(project(':grpc-core').path)
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':grpc-core'),
|
||||||
|
project(':grpc-protobuf'),
|
||||||
|
project(':grpc-stub')
|
||||||
|
compileOnly libraries.javax_annotation
|
||||||
|
testCompile libraries.truth
|
||||||
|
}
|
||||||
|
|
||||||
|
configureProtoCompilation()
|
||||||
|
|
||||||
|
// do not publish 'grpc-rls'
|
||||||
|
[publishMavenPublicationToMavenRepository]*.onlyIf { false }
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
package io.grpc.lookup.v1;
|
||||||
|
|
||||||
|
import static io.grpc.MethodDescriptor.generateFullMethodName;
|
||||||
|
import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.asyncUnaryCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.blockingUnaryCall;
|
||||||
|
import static io.grpc.stub.ClientCalls.futureUnaryCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncUnaryCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
|
||||||
|
import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@javax.annotation.Generated(
|
||||||
|
value = "by gRPC proto compiler",
|
||||||
|
comments = "Source: grpc/lookup/v1/rls.proto")
|
||||||
|
public final class RouteLookupServiceGrpc {
|
||||||
|
|
||||||
|
private RouteLookupServiceGrpc() {}
|
||||||
|
|
||||||
|
public static final String SERVICE_NAME = "grpc.lookup.v1.RouteLookupService";
|
||||||
|
|
||||||
|
// Static method descriptors that strictly reflect the proto.
|
||||||
|
private static volatile io.grpc.MethodDescriptor<io.grpc.lookup.v1.RouteLookupRequest,
|
||||||
|
io.grpc.lookup.v1.RouteLookupResponse> getRouteLookupMethod;
|
||||||
|
|
||||||
|
@io.grpc.stub.annotations.RpcMethod(
|
||||||
|
fullMethodName = SERVICE_NAME + '/' + "RouteLookup",
|
||||||
|
requestType = io.grpc.lookup.v1.RouteLookupRequest.class,
|
||||||
|
responseType = io.grpc.lookup.v1.RouteLookupResponse.class,
|
||||||
|
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||||
|
public static io.grpc.MethodDescriptor<io.grpc.lookup.v1.RouteLookupRequest,
|
||||||
|
io.grpc.lookup.v1.RouteLookupResponse> getRouteLookupMethod() {
|
||||||
|
io.grpc.MethodDescriptor<io.grpc.lookup.v1.RouteLookupRequest, io.grpc.lookup.v1.RouteLookupResponse> getRouteLookupMethod;
|
||||||
|
if ((getRouteLookupMethod = RouteLookupServiceGrpc.getRouteLookupMethod) == null) {
|
||||||
|
synchronized (RouteLookupServiceGrpc.class) {
|
||||||
|
if ((getRouteLookupMethod = RouteLookupServiceGrpc.getRouteLookupMethod) == null) {
|
||||||
|
RouteLookupServiceGrpc.getRouteLookupMethod = getRouteLookupMethod =
|
||||||
|
io.grpc.MethodDescriptor.<io.grpc.lookup.v1.RouteLookupRequest, io.grpc.lookup.v1.RouteLookupResponse>newBuilder()
|
||||||
|
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
|
||||||
|
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "RouteLookup"))
|
||||||
|
.setSampledToLocalTracing(true)
|
||||||
|
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||||
|
io.grpc.lookup.v1.RouteLookupRequest.getDefaultInstance()))
|
||||||
|
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
|
||||||
|
io.grpc.lookup.v1.RouteLookupResponse.getDefaultInstance()))
|
||||||
|
.setSchemaDescriptor(new RouteLookupServiceMethodDescriptorSupplier("RouteLookup"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getRouteLookupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new async stub that supports all call types for the service
|
||||||
|
*/
|
||||||
|
public static RouteLookupServiceStub newStub(io.grpc.Channel channel) {
|
||||||
|
io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceStub> factory =
|
||||||
|
new io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceStub>() {
|
||||||
|
@java.lang.Override
|
||||||
|
public RouteLookupServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return RouteLookupServiceStub.newStub(factory, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new blocking-style stub that supports unary and streaming output calls on the service
|
||||||
|
*/
|
||||||
|
public static RouteLookupServiceBlockingStub newBlockingStub(
|
||||||
|
io.grpc.Channel channel) {
|
||||||
|
io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceBlockingStub> factory =
|
||||||
|
new io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceBlockingStub>() {
|
||||||
|
@java.lang.Override
|
||||||
|
public RouteLookupServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceBlockingStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return RouteLookupServiceBlockingStub.newStub(factory, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ListenableFuture-style stub that supports unary calls on the service
|
||||||
|
*/
|
||||||
|
public static RouteLookupServiceFutureStub newFutureStub(
|
||||||
|
io.grpc.Channel channel) {
|
||||||
|
io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceFutureStub> factory =
|
||||||
|
new io.grpc.stub.AbstractStub.StubFactory<RouteLookupServiceFutureStub>() {
|
||||||
|
@java.lang.Override
|
||||||
|
public RouteLookupServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceFutureStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return RouteLookupServiceFutureStub.newStub(factory, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static abstract class RouteLookupServiceImplBase implements io.grpc.BindableService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Lookup returns a target for a single key.
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void routeLookup(io.grpc.lookup.v1.RouteLookupRequest request,
|
||||||
|
io.grpc.stub.StreamObserver<io.grpc.lookup.v1.RouteLookupResponse> responseObserver) {
|
||||||
|
asyncUnimplementedUnaryCall(getRouteLookupMethod(), responseObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
|
||||||
|
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
|
||||||
|
.addMethod(
|
||||||
|
getRouteLookupMethod(),
|
||||||
|
asyncUnaryCall(
|
||||||
|
new MethodHandlers<
|
||||||
|
io.grpc.lookup.v1.RouteLookupRequest,
|
||||||
|
io.grpc.lookup.v1.RouteLookupResponse>(
|
||||||
|
this, METHODID_ROUTE_LOOKUP)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupServiceStub extends io.grpc.stub.AbstractAsyncStub<RouteLookupServiceStub> {
|
||||||
|
private RouteLookupServiceStub(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
super(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
protected RouteLookupServiceStub build(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Lookup returns a target for a single key.
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public void routeLookup(io.grpc.lookup.v1.RouteLookupRequest request,
|
||||||
|
io.grpc.stub.StreamObserver<io.grpc.lookup.v1.RouteLookupResponse> responseObserver) {
|
||||||
|
asyncUnaryCall(
|
||||||
|
getChannel().newCall(getRouteLookupMethod(), getCallOptions()), request, responseObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub<RouteLookupServiceBlockingStub> {
|
||||||
|
private RouteLookupServiceBlockingStub(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
super(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
protected RouteLookupServiceBlockingStub build(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceBlockingStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Lookup returns a target for a single key.
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public io.grpc.lookup.v1.RouteLookupResponse routeLookup(io.grpc.lookup.v1.RouteLookupRequest request) {
|
||||||
|
return blockingUnaryCall(
|
||||||
|
getChannel(), getRouteLookupMethod(), getCallOptions(), request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupServiceFutureStub extends io.grpc.stub.AbstractFutureStub<RouteLookupServiceFutureStub> {
|
||||||
|
private RouteLookupServiceFutureStub(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
super(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
protected RouteLookupServiceFutureStub build(
|
||||||
|
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
|
||||||
|
return new RouteLookupServiceFutureStub(channel, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Lookup returns a target for a single key.
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public com.google.common.util.concurrent.ListenableFuture<io.grpc.lookup.v1.RouteLookupResponse> routeLookup(
|
||||||
|
io.grpc.lookup.v1.RouteLookupRequest request) {
|
||||||
|
return futureUnaryCall(
|
||||||
|
getChannel().newCall(getRouteLookupMethod(), getCallOptions()), request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int METHODID_ROUTE_LOOKUP = 0;
|
||||||
|
|
||||||
|
private static final class MethodHandlers<Req, Resp> implements
|
||||||
|
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
|
||||||
|
io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
|
||||||
|
io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
|
||||||
|
io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
|
||||||
|
private final RouteLookupServiceImplBase serviceImpl;
|
||||||
|
private final int methodId;
|
||||||
|
|
||||||
|
MethodHandlers(RouteLookupServiceImplBase serviceImpl, int methodId) {
|
||||||
|
this.serviceImpl = serviceImpl;
|
||||||
|
this.methodId = methodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
@java.lang.SuppressWarnings("unchecked")
|
||||||
|
public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
|
||||||
|
switch (methodId) {
|
||||||
|
case METHODID_ROUTE_LOOKUP:
|
||||||
|
serviceImpl.routeLookup((io.grpc.lookup.v1.RouteLookupRequest) request,
|
||||||
|
(io.grpc.stub.StreamObserver<io.grpc.lookup.v1.RouteLookupResponse>) responseObserver);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
@java.lang.SuppressWarnings("unchecked")
|
||||||
|
public io.grpc.stub.StreamObserver<Req> invoke(
|
||||||
|
io.grpc.stub.StreamObserver<Resp> responseObserver) {
|
||||||
|
switch (methodId) {
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class RouteLookupServiceBaseDescriptorSupplier
|
||||||
|
implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
|
||||||
|
RouteLookupServiceBaseDescriptorSupplier() {}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
|
||||||
|
return io.grpc.lookup.v1.RlsProto.getDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
|
||||||
|
return getFileDescriptor().findServiceByName("RouteLookupService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RouteLookupServiceFileDescriptorSupplier
|
||||||
|
extends RouteLookupServiceBaseDescriptorSupplier {
|
||||||
|
RouteLookupServiceFileDescriptorSupplier() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RouteLookupServiceMethodDescriptorSupplier
|
||||||
|
extends RouteLookupServiceBaseDescriptorSupplier
|
||||||
|
implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
|
||||||
|
private final String methodName;
|
||||||
|
|
||||||
|
RouteLookupServiceMethodDescriptorSupplier(String methodName) {
|
||||||
|
this.methodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@java.lang.Override
|
||||||
|
public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
|
||||||
|
return getServiceDescriptor().findMethodByName(methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
|
||||||
|
|
||||||
|
public static io.grpc.ServiceDescriptor getServiceDescriptor() {
|
||||||
|
io.grpc.ServiceDescriptor result = serviceDescriptor;
|
||||||
|
if (result == null) {
|
||||||
|
synchronized (RouteLookupServiceGrpc.class) {
|
||||||
|
result = serviceDescriptor;
|
||||||
|
if (result == null) {
|
||||||
|
serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
|
||||||
|
.setSchemaDescriptor(new RouteLookupServiceFileDescriptorSupplier())
|
||||||
|
.addMethod(getRouteLookupMethod())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.rls.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
import com.google.common.base.Converter;
|
||||||
|
import io.grpc.internal.JsonUtil;
|
||||||
|
import io.grpc.lookup.v1.RouteLookupRequest;
|
||||||
|
import io.grpc.lookup.v1.RouteLookupResponse;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.GrpcKeyBuilder;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.GrpcKeyBuilder.Name;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.NameMatcher;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.RequestProcessingStrategy;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.RouteLookupConfig;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RlsProtoConverters is a collection of {@link Converter} between RouteLookupService proto / json
|
||||||
|
* messages to internal representation in {@link RlsProtoData}.
|
||||||
|
*/
|
||||||
|
public final class RlsProtoConverters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RouteLookupRequestConverter converts between {@link RouteLookupRequest} and {@link
|
||||||
|
* RlsProtoData.RouteLookupRequest}.
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupRequestConverter
|
||||||
|
extends Converter<RouteLookupRequest, RlsProtoData.RouteLookupRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RlsProtoData.RouteLookupRequest doForward(RouteLookupRequest routeLookupRequest) {
|
||||||
|
return
|
||||||
|
new RlsProtoData.RouteLookupRequest(
|
||||||
|
/* server= */ routeLookupRequest.getServer(),
|
||||||
|
/* path= */ routeLookupRequest.getPath(),
|
||||||
|
/* targetType= */ routeLookupRequest.getTargetType(),
|
||||||
|
routeLookupRequest.getKeyMapMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RouteLookupRequest doBackward(RlsProtoData.RouteLookupRequest routeLookupRequest) {
|
||||||
|
return
|
||||||
|
RouteLookupRequest.newBuilder()
|
||||||
|
.setServer(routeLookupRequest.getServer())
|
||||||
|
.setPath(routeLookupRequest.getPath())
|
||||||
|
.setTargetType(routeLookupRequest.getTargetType())
|
||||||
|
.putAllKeyMap(routeLookupRequest.getKeyMap())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RouteLookupResponseConverter converts between {@link RouteLookupResponse} and {@link
|
||||||
|
* RlsProtoData.RouteLookupResponse}.
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupResponseConverter
|
||||||
|
extends Converter<RouteLookupResponse, RlsProtoData.RouteLookupResponse> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RlsProtoData.RouteLookupResponse doForward(RouteLookupResponse routeLookupResponse) {
|
||||||
|
return
|
||||||
|
new RlsProtoData.RouteLookupResponse(
|
||||||
|
routeLookupResponse.getTarget(),
|
||||||
|
routeLookupResponse.getHeaderData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RouteLookupResponse doBackward(RlsProtoData.RouteLookupResponse routeLookupResponse) {
|
||||||
|
return RouteLookupResponse.newBuilder()
|
||||||
|
.setTarget(routeLookupResponse.getTarget())
|
||||||
|
.setHeaderData(routeLookupResponse.getHeaderData())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RouteLookupConfigConverter converts between json map to {@link RouteLookupConfig}.
|
||||||
|
*/
|
||||||
|
public static final class RouteLookupConfigConverter
|
||||||
|
extends Converter<Map<String, ?>, RouteLookupConfig> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RouteLookupConfig doForward(Map<String, ?> json) {
|
||||||
|
List<GrpcKeyBuilder> grpcKeyBuilders =
|
||||||
|
GrpcKeyBuilderConverter
|
||||||
|
.covertAll(JsonUtil.checkObjectList(JsonUtil.getList(json, "grpcKeyBuilders")));
|
||||||
|
String lookupService = JsonUtil.getString(json, "lookupService");
|
||||||
|
long timeout =
|
||||||
|
TimeUnit.SECONDS.toMillis(JsonUtil.getNumberAsLong(json, "lookupServiceTimeout"));
|
||||||
|
Long maxAge =
|
||||||
|
convertTimeIfNotNull(
|
||||||
|
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, JsonUtil.getNumberAsLong(json, "maxAge"));
|
||||||
|
Long staleAge =
|
||||||
|
convertTimeIfNotNull(
|
||||||
|
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, JsonUtil.getNumberAsLong(json, "staleAge"));
|
||||||
|
long cacheSize = JsonUtil.getNumberAsLong(json, "cacheSizeBytes");
|
||||||
|
List<String> validTargets = JsonUtil.checkStringList(JsonUtil.getList(json, "validTargets"));
|
||||||
|
String defaultTarget = JsonUtil.getString(json, "defaultTarget");
|
||||||
|
RequestProcessingStrategy strategy =
|
||||||
|
RequestProcessingStrategy
|
||||||
|
.valueOf(JsonUtil.getString(json, "requestProcessingStrategy").toUpperCase());
|
||||||
|
return new RouteLookupConfig(
|
||||||
|
grpcKeyBuilders,
|
||||||
|
lookupService,
|
||||||
|
/* lookupServiceTimeoutInMillis= */ timeout,
|
||||||
|
/* maxAgeInMillis= */ maxAge,
|
||||||
|
/* staleAgeInMillis= */ staleAge,
|
||||||
|
/* cacheSizeBytes= */ cacheSize,
|
||||||
|
validTargets,
|
||||||
|
defaultTarget,
|
||||||
|
strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Long convertTimeIfNotNull(TimeUnit from, TimeUnit to, Long value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return to.convert(value, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> doBackward(RouteLookupConfig routeLookupConfig) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GrpcKeyBuilderConverter {
|
||||||
|
public static List<GrpcKeyBuilder> covertAll(List<Map<String, ?>> keyBuilders) {
|
||||||
|
List<GrpcKeyBuilder> keyBuilderList = new ArrayList<>();
|
||||||
|
for (Map<String, ?> keyBuilder : keyBuilders) {
|
||||||
|
keyBuilderList.add(convert(keyBuilder));
|
||||||
|
}
|
||||||
|
return keyBuilderList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static GrpcKeyBuilder convert(Map<String, ?> keyBuilder) {
|
||||||
|
List<Map<String, ?>> rawNames =
|
||||||
|
JsonUtil.checkObjectList(JsonUtil.getList(keyBuilder, "names"));
|
||||||
|
List<Name> names = new ArrayList<>();
|
||||||
|
for (Map<String, ?> rawName : rawNames) {
|
||||||
|
names.add(
|
||||||
|
new Name(
|
||||||
|
JsonUtil.getString(rawName, "service"), JsonUtil.getString(rawName, "method")));
|
||||||
|
}
|
||||||
|
List<Map<String, ?>> rawHeaders =
|
||||||
|
JsonUtil.checkObjectList(JsonUtil.getList(keyBuilder, "headers"));
|
||||||
|
List<NameMatcher> nameMatchers = new ArrayList<>();
|
||||||
|
for (Map<String, ?> rawHeader : rawHeaders) {
|
||||||
|
NameMatcher matcher =
|
||||||
|
new NameMatcher(
|
||||||
|
JsonUtil.getString(rawHeader, "key"),
|
||||||
|
(List<String>) rawHeader.get("names"),
|
||||||
|
(Boolean) rawHeader.get("optional"));
|
||||||
|
checkArgument(
|
||||||
|
matcher.isOptional(), "NameMatcher for GrpcKeyBuilders shouldn't be required");
|
||||||
|
nameMatchers.add(matcher);
|
||||||
|
}
|
||||||
|
return new GrpcKeyBuilder(names, nameMatchers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,595 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.rls.internal;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.GrpcKeyBuilder.Name;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/** RlsProtoData is a collection of internal representation of RouteLookupService proto messages. */
|
||||||
|
public final class RlsProtoData {
|
||||||
|
|
||||||
|
/** A request object sent to route lookup service. */
|
||||||
|
@Immutable
|
||||||
|
public static final class RouteLookupRequest {
|
||||||
|
|
||||||
|
private final String server;
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
private final String targetType;
|
||||||
|
|
||||||
|
private final ImmutableMap<String, String> keyMap;
|
||||||
|
|
||||||
|
/** Constructor for RouteLookupRequest. */
|
||||||
|
public RouteLookupRequest(
|
||||||
|
String server, String path, String targetType, Map<String, String> keyMap) {
|
||||||
|
this.server = checkNotNull(server, "server");
|
||||||
|
this.path = checkNotNull(path, "path");
|
||||||
|
this.targetType = checkNotNull(targetType, "targetName");
|
||||||
|
this.keyMap = ImmutableMap.copyOf(checkNotNull(keyMap, "keyMap"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a full host name of the target server, {@literal e.g.} firestore.googleapis.com. Only
|
||||||
|
* set for gRPC requests; HTTP requests must use key_map explicitly.
|
||||||
|
*/
|
||||||
|
public String getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a full path of the request, {@literal i.e.} "/service/method". Only set for gRPC
|
||||||
|
* requests; HTTP requests must use key_map explicitly.
|
||||||
|
*/
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target type allows the client to specify what kind of target format it would like
|
||||||
|
* from RLS to allow it to find the regional server, {@literal e.g.} "grpc".
|
||||||
|
*/
|
||||||
|
public String getTargetType() {
|
||||||
|
return targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a map of key values extracted via key builders for the gRPC or HTTP request. */
|
||||||
|
public ImmutableMap<String, String> getKeyMap() {
|
||||||
|
return keyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RouteLookupRequest that = (RouteLookupRequest) o;
|
||||||
|
return Objects.equal(server, that.server)
|
||||||
|
&& Objects.equal(path, that.path)
|
||||||
|
&& Objects.equal(targetType, that.targetType)
|
||||||
|
&& Objects.equal(keyMap, that.keyMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(server, path, targetType, keyMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("server", server)
|
||||||
|
.add("path", path)
|
||||||
|
.add("targetName", targetType)
|
||||||
|
.add("keyMap", keyMap)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A response from route lookup service. */
|
||||||
|
@Immutable
|
||||||
|
public static final class RouteLookupResponse {
|
||||||
|
|
||||||
|
private final String target;
|
||||||
|
|
||||||
|
private final String headerData;
|
||||||
|
|
||||||
|
public RouteLookupResponse(String target, String headerData) {
|
||||||
|
this.target = checkNotNull(target, "target");
|
||||||
|
this.headerData = checkNotNull(headerData, "headerData");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns target. A target is an actual addressable entity to use for routing decision, using
|
||||||
|
* syntax requested by the request target_type.
|
||||||
|
*/
|
||||||
|
public String getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns optional header data to pass along to AFE in the X-Google-RLS-Data header. Cached
|
||||||
|
* with "target" and sent with all requests that match the request key. Allows the RLS to pass
|
||||||
|
* its work product to the eventual target.
|
||||||
|
*/
|
||||||
|
public String getHeaderData() {
|
||||||
|
return headerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RouteLookupResponse that = (RouteLookupResponse) o;
|
||||||
|
return java.util.Objects.equals(target, that.target)
|
||||||
|
&& java.util.Objects.equals(headerData, that.headerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return java.util.Objects.hash(target, headerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("target", target)
|
||||||
|
.add("headerData", headerData)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A config object for gRPC RouteLookupService. */
|
||||||
|
@Immutable
|
||||||
|
public static final class RouteLookupConfig {
|
||||||
|
|
||||||
|
private static final long MAX_AGE_MILLIS = TimeUnit.MINUTES.toMillis(5);
|
||||||
|
|
||||||
|
private final ImmutableList<GrpcKeyBuilder> grpcKeyBuilders;
|
||||||
|
|
||||||
|
private final String lookupService;
|
||||||
|
|
||||||
|
private final long lookupServiceTimeoutInMillis;
|
||||||
|
|
||||||
|
private final long maxAgeInMillis;
|
||||||
|
|
||||||
|
private final long staleAgeInMillis;
|
||||||
|
|
||||||
|
private final long cacheSizeBytes;
|
||||||
|
|
||||||
|
private final ImmutableList<String> validTargets;
|
||||||
|
|
||||||
|
private final String defaultTarget;
|
||||||
|
|
||||||
|
private final RequestProcessingStrategy requestProcessingStrategy;
|
||||||
|
|
||||||
|
/** Constructs RouteLookupConfig. */
|
||||||
|
public RouteLookupConfig(
|
||||||
|
List<GrpcKeyBuilder> grpcKeyBuilders,
|
||||||
|
String lookupService,
|
||||||
|
long lookupServiceTimeoutInMillis,
|
||||||
|
@Nullable Long maxAgeInMillis,
|
||||||
|
@Nullable Long staleAgeInMillis,
|
||||||
|
long cacheSizeBytes,
|
||||||
|
List<String> validTargets,
|
||||||
|
String defaultTarget,
|
||||||
|
RequestProcessingStrategy requestProcessingStrategy) {
|
||||||
|
checkState(
|
||||||
|
!checkNotNull(grpcKeyBuilders, "grpcKeyBuilders").isEmpty(),
|
||||||
|
"must have at least one GrpcKeyBuilder");
|
||||||
|
checkUniqueName(grpcKeyBuilders);
|
||||||
|
this.grpcKeyBuilders = ImmutableList.copyOf(grpcKeyBuilders);
|
||||||
|
// TODO(creamsoup) also check if it is URI
|
||||||
|
checkState(
|
||||||
|
lookupService != null && !lookupService.isEmpty(), "lookupService must not be empty");
|
||||||
|
this.lookupService = lookupService;
|
||||||
|
this.lookupServiceTimeoutInMillis = lookupServiceTimeoutInMillis;
|
||||||
|
if (maxAgeInMillis == null) {
|
||||||
|
checkState(
|
||||||
|
staleAgeInMillis == null, "To specify staleAgeInMillis, must have maxAgeInMillis");
|
||||||
|
}
|
||||||
|
if (maxAgeInMillis == null || maxAgeInMillis == 0) {
|
||||||
|
maxAgeInMillis = MAX_AGE_MILLIS;
|
||||||
|
}
|
||||||
|
if (staleAgeInMillis == null || staleAgeInMillis == 0) {
|
||||||
|
staleAgeInMillis = MAX_AGE_MILLIS;
|
||||||
|
}
|
||||||
|
this.maxAgeInMillis = Math.min(maxAgeInMillis, MAX_AGE_MILLIS);
|
||||||
|
this.staleAgeInMillis = Math.min(staleAgeInMillis, this.maxAgeInMillis);
|
||||||
|
checkArgument(cacheSizeBytes > 0, "cacheSize must be positive");
|
||||||
|
this.cacheSizeBytes = cacheSizeBytes;
|
||||||
|
this.validTargets = ImmutableList.copyOf(checkNotNull(validTargets, "validTargets"));
|
||||||
|
this.defaultTarget = checkNotNull(defaultTarget, "defaultTarget");
|
||||||
|
this.requestProcessingStrategy = requestProcessingStrategy;
|
||||||
|
checkNotNull(requestProcessingStrategy, "requestProcessingStrategy");
|
||||||
|
checkState(
|
||||||
|
(requestProcessingStrategy == RequestProcessingStrategy.SYNC_LOOKUP_CLIENT_SEES_ERROR
|
||||||
|
|| requestProcessingStrategy
|
||||||
|
== RequestProcessingStrategy.ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS)
|
||||||
|
&& !defaultTarget.isEmpty(),
|
||||||
|
"defaultTarget cannot be empty if strategy is %s",
|
||||||
|
requestProcessingStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unordered specifications for constructing keys for gRPC requests. All GrpcKeyBuilders
|
||||||
|
* on this list must have unique "name" fields so that the client is free to prebuild a hash map
|
||||||
|
* keyed by name. If no GrpcKeyBuilder matches, an empty key_map will be sent to the lookup
|
||||||
|
* service; it should likely reply with a global default route and raise an alert.
|
||||||
|
*/
|
||||||
|
public ImmutableList<GrpcKeyBuilder> getGrpcKeyBuilders() {
|
||||||
|
return grpcKeyBuilders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the lookup service as a gRPC URI. Typically, this will be a subdomain of
|
||||||
|
* the target, such as "lookup.datastore.googleapis.com".
|
||||||
|
*/
|
||||||
|
public String getLookupService() {
|
||||||
|
return lookupService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the timeout value for lookup service requests. */
|
||||||
|
public long getLookupServiceTimeoutInMillis() {
|
||||||
|
return lookupServiceTimeoutInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns the maximum age the result will be cached. */
|
||||||
|
public long getMaxAgeInMillis() {
|
||||||
|
return maxAgeInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time when an entry will be in a staled status. When cache is accessed whgen the
|
||||||
|
* entry is in staled status, it will
|
||||||
|
*/
|
||||||
|
public long getStaleAgeInMillis() {
|
||||||
|
return staleAgeInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a rough indicator of amount of memory to use for the client cache. Some of the data
|
||||||
|
* structure overhead is not accounted for, so actual memory consumed will be somewhat greater
|
||||||
|
* than this value. If this field is omitted or set to zero, a client default will be used.
|
||||||
|
* The value may be capped to a lower amount based on client configuration.
|
||||||
|
*/
|
||||||
|
public long getCacheSizeBytes() {
|
||||||
|
return cacheSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of all the possible targets that can be returned by the lookup service. If
|
||||||
|
* a target not on this list is returned, it will be treated the same as an RPC error from the
|
||||||
|
* RLS.
|
||||||
|
*/
|
||||||
|
public ImmutableList<String> getValidTargets() {
|
||||||
|
return validTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default target to use. It will be used for request processing strategy
|
||||||
|
* {@link RequestProcessingStrategy#SYNC_LOOKUP_DEFAULT_TARGET_ON_ERROR} if RLS
|
||||||
|
* returns an error, or strategy {@link
|
||||||
|
* RequestProcessingStrategy#ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS} if RLS returns an error or
|
||||||
|
* there is a cache miss in the client. It will also be used if there are no healthy backends
|
||||||
|
* for an RLS target. Note that requests can be routed only to a subdomain of the original
|
||||||
|
* target, {@literal e.g.} "us_east_1.cloudbigtable.googleapis.com".
|
||||||
|
*/
|
||||||
|
public String getDefaultTarget() {
|
||||||
|
return defaultTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns {@link RequestProcessingStrategy} to process RLS response. */
|
||||||
|
public RequestProcessingStrategy getRequestProcessingStrategy() {
|
||||||
|
return requestProcessingStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RouteLookupConfig that = (RouteLookupConfig) o;
|
||||||
|
return lookupServiceTimeoutInMillis == that.lookupServiceTimeoutInMillis
|
||||||
|
&& maxAgeInMillis == that.maxAgeInMillis
|
||||||
|
&& staleAgeInMillis == that.staleAgeInMillis
|
||||||
|
&& cacheSizeBytes == that.cacheSizeBytes
|
||||||
|
&& Objects.equal(grpcKeyBuilders, that.grpcKeyBuilders)
|
||||||
|
&& Objects.equal(lookupService, that.lookupService)
|
||||||
|
&& Objects.equal(defaultTarget, that.defaultTarget)
|
||||||
|
&& requestProcessingStrategy == that.requestProcessingStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(
|
||||||
|
grpcKeyBuilders,
|
||||||
|
lookupService,
|
||||||
|
lookupServiceTimeoutInMillis,
|
||||||
|
maxAgeInMillis,
|
||||||
|
staleAgeInMillis,
|
||||||
|
cacheSizeBytes,
|
||||||
|
defaultTarget,
|
||||||
|
requestProcessingStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("grpcKeyBuilders", grpcKeyBuilders)
|
||||||
|
.add("lookupService", lookupService)
|
||||||
|
.add("lookupServiceTimeoutInMillis", lookupServiceTimeoutInMillis)
|
||||||
|
.add("maxAgeInMillis", maxAgeInMillis)
|
||||||
|
.add("staleAgeInMillis", staleAgeInMillis)
|
||||||
|
.add("cacheSize", cacheSizeBytes)
|
||||||
|
.add("defaultTarget", defaultTarget)
|
||||||
|
.add("requestProcessingStrategy", requestProcessingStrategy)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkUniqueName(List<GrpcKeyBuilder> grpcKeyBuilders) {
|
||||||
|
Set<Name> names = new HashSet<>();
|
||||||
|
for (GrpcKeyBuilder grpcKeyBuilder : grpcKeyBuilders) {
|
||||||
|
int prevSize = names.size();
|
||||||
|
names.addAll(grpcKeyBuilder.getNames());
|
||||||
|
if (names.size() != prevSize + grpcKeyBuilder.getNames().size()) {
|
||||||
|
throw new IllegalStateException("Names in the GrpcKeyBuilders should be unique");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** RequestProcessingStrategy specifies how to process a request when not already in the cache. */
|
||||||
|
enum RequestProcessingStrategy {
|
||||||
|
/**
|
||||||
|
* Query the RLS and process the request using target returned by the lookup. The target will
|
||||||
|
* then be cached and used for processing subsequent requests for the same key. Any errors
|
||||||
|
* during lookup service processing will fall back to default target for request processing.
|
||||||
|
*/
|
||||||
|
SYNC_LOOKUP_DEFAULT_TARGET_ON_ERROR,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the RLS and process the request using target returned by the lookup. The target will
|
||||||
|
* then be cached and used for processing subsequent requests for the same key. Any errors
|
||||||
|
* during lookup service processing will return an error back to the client. Services with
|
||||||
|
* strict regional routing requirements should use this strategy.
|
||||||
|
*/
|
||||||
|
SYNC_LOOKUP_CLIENT_SEES_ERROR,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the RLS asynchronously but respond with the default target. The target in the lookup
|
||||||
|
* response will then be cached and used for subsequent requests. Services with strict latency
|
||||||
|
* requirements (but not strict regional routing requirements) should use this strategy.
|
||||||
|
*/
|
||||||
|
ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NameMatcher extract a key based on a given name (e.g. header name or query parameter name).
|
||||||
|
* The name must match one of the names listed in the "name" field. If the "required_match" field
|
||||||
|
* is true, one of the specified names must be present for the keybuilder to match.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
static final class NameMatcher {
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
private final ImmutableList<String> names;
|
||||||
|
|
||||||
|
private final boolean optional;
|
||||||
|
|
||||||
|
NameMatcher(String key, List<String> names, boolean optional) {
|
||||||
|
this.key = checkNotNull(key, "key");
|
||||||
|
this.names = ImmutableList.copyOf(checkNotNull(names, "names"));
|
||||||
|
this.optional = optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The name that will be used in the RLS key_map to refer to this value. */
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns ordered list of names; the first non-empty value will be used. */
|
||||||
|
public ImmutableList<String> names() {
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this extraction optional. A key builder will still match if no value is found.
|
||||||
|
*/
|
||||||
|
public boolean isOptional() {
|
||||||
|
return optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NameMatcher matcher = (NameMatcher) o;
|
||||||
|
return optional == matcher.optional
|
||||||
|
&& java.util.Objects.equals(key, matcher.key)
|
||||||
|
&& java.util.Objects.equals(names, matcher.names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return java.util.Objects.hash(key, names, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("key", key)
|
||||||
|
.add("names", names)
|
||||||
|
.add("optional", optional)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GrpcKeyBuilder is a configuration to construct headers consumed by route lookup service. */
|
||||||
|
static final class GrpcKeyBuilder {
|
||||||
|
|
||||||
|
private final ImmutableList<Name> names;
|
||||||
|
|
||||||
|
private final ImmutableList<NameMatcher> headers;
|
||||||
|
|
||||||
|
public GrpcKeyBuilder(List<Name> names, List<NameMatcher> headers) {
|
||||||
|
checkState(names != null && !names.isEmpty(), "names cannot be empty");
|
||||||
|
this.names = ImmutableList.copyOf(names);
|
||||||
|
checkUniqueKey(checkNotNull(headers, "headers"));
|
||||||
|
this.headers = ImmutableList.copyOf(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkUniqueKey(List<NameMatcher> headers) {
|
||||||
|
Set<String> names = new HashSet<>();
|
||||||
|
for (NameMatcher header : headers) {
|
||||||
|
checkState(names.add(header.key), "key in headers must be unique");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns names. To match, one of the given Name fields must match; the service and method
|
||||||
|
* fields are specified as fixed strings. The service name is required and includes the proto
|
||||||
|
* package name. The method name may be omitted, in which case any method on the given service
|
||||||
|
* is matched.
|
||||||
|
*/
|
||||||
|
public ImmutableList<Name> getNames() {
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of NameMatchers for header. Extract keys from all listed headers. For gRPC, it
|
||||||
|
* is an error to specify "required_match" on the NameMatcher protos, and we ignore it if set.
|
||||||
|
*/
|
||||||
|
public ImmutableList<NameMatcher> getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GrpcKeyBuilder that = (GrpcKeyBuilder) o;
|
||||||
|
return Objects.equal(names, that.names) && Objects.equal(headers, that.headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(names, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("names", names)
|
||||||
|
.add("headers", headers)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name represents a method for a given service. To match, one of the given Name fields must
|
||||||
|
* match; the service and method fields are specified as fixed strings. The service name is
|
||||||
|
* required and includes the proto package name. The method name may be omitted, in which case
|
||||||
|
* any method on the given service is matched.
|
||||||
|
*/
|
||||||
|
static final class Name {
|
||||||
|
|
||||||
|
private final String service;
|
||||||
|
|
||||||
|
private final String method;
|
||||||
|
|
||||||
|
public Name(String service) {
|
||||||
|
this(service, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name(String service, String method) {
|
||||||
|
checkState(
|
||||||
|
!checkNotNull(service, "service").isEmpty(),
|
||||||
|
"service must not be empty or null");
|
||||||
|
this.service = service;
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getService() {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Name name = (Name) o;
|
||||||
|
return Objects.equal(service, name.service)
|
||||||
|
&& Objects.equal(method, name.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(service, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("service", service)
|
||||||
|
.add("method", method)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2020 The gRPC Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package grpc.lookup.v1;
|
||||||
|
|
||||||
|
option go_package = "google.golang.org/grpc/lookup/grpc_lookup_v1";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.lookup.v1";
|
||||||
|
option java_outer_classname = "RlsProto";
|
||||||
|
|
||||||
|
message RouteLookupRequest {
|
||||||
|
// Full host name of the target server, e.g. firestore.googleapis.com.
|
||||||
|
// Only set for gRPC requests; HTTP requests must use key_map explicitly.
|
||||||
|
string server = 1;
|
||||||
|
// Full path of the request, i.e. "/service/method".
|
||||||
|
// Only set for gRPC requests; HTTP requests must use key_map explicitly.
|
||||||
|
string path = 2;
|
||||||
|
// Target type allows the client to specify what kind of target format it
|
||||||
|
// would like from RLS to allow it to find the regional server, e.g. "grpc".
|
||||||
|
string target_type = 3;
|
||||||
|
// Map of key values extracted via key builders for the gRPC or HTTP request.
|
||||||
|
map<string, string> key_map = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RouteLookupResponse {
|
||||||
|
// Actual addressable entity to use for routing decision, using syntax
|
||||||
|
// requested by the request target_type.
|
||||||
|
string target = 1;
|
||||||
|
// Optional header value to pass along to AFE in the X-Google-RLS-Data header.
|
||||||
|
// Cached with "target" and sent with all requests that match the request key.
|
||||||
|
// Allows the RLS to pass its work product to the eventual target.
|
||||||
|
string header_data = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service RouteLookupService {
|
||||||
|
// Lookup returns a target for a single key.
|
||||||
|
rpc RouteLookup(RouteLookupRequest) returns (RouteLookupResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright 2020 The gRPC Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package grpc.lookup.v1;
|
||||||
|
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
option go_package = "google.golang.org/grpc/lookup/grpc_lookup_v1";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.lookup.v1";
|
||||||
|
option java_outer_classname = "RlsConfigProto";
|
||||||
|
|
||||||
|
// Extract a key based on a given name (e.g. header name or query parameter
|
||||||
|
// name). The name must match one of the names listed in the "name" field. If
|
||||||
|
// the "required_match" field is true, one of the specified names must be
|
||||||
|
// present for the keybuilder to match.
|
||||||
|
message NameMatcher {
|
||||||
|
// The name that will be used in the RLS key_map to refer to this value.
|
||||||
|
string key = 1;
|
||||||
|
|
||||||
|
// Ordered list of names (headers or query parameter names) that can supply
|
||||||
|
// this value; the first one with a non-empty value is used.
|
||||||
|
repeated string names = 2;
|
||||||
|
|
||||||
|
// If true, make this extraction required; the key builder will not match
|
||||||
|
// if no value is found.
|
||||||
|
bool required_match = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A GrpcKeyBuilder applies to a given gRPC service, name, and headers.
|
||||||
|
message GrpcKeyBuilder {
|
||||||
|
// To match, one of the given Name fields must match; the service and method
|
||||||
|
// fields are specified as fixed strings. The service name is required and
|
||||||
|
// includes the proto package name. The method name may be omitted, in
|
||||||
|
// which case any method on the given service is matched.
|
||||||
|
message Name {
|
||||||
|
string service = 1;
|
||||||
|
string method = 2;
|
||||||
|
}
|
||||||
|
repeated Name names = 1;
|
||||||
|
|
||||||
|
// Extract keys from all listed headers.
|
||||||
|
// For gRPC, it is an error to specify "required_match" on the NameMatcher
|
||||||
|
// protos, and we ignore it if set.
|
||||||
|
repeated NameMatcher headers = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An HttpKeyBuilder applies to a given HTTP URL and headers.
|
||||||
|
//
|
||||||
|
// Path and host patterns use the matching syntax from gRPC transcoding to
|
||||||
|
// extract named key/value pairs from the path and host components of the URL:
|
||||||
|
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
|
||||||
|
//
|
||||||
|
// It is invalid to specify the same key name in multiple places in a pattern.
|
||||||
|
//
|
||||||
|
// For a service where the project id can be expressed either as a subdomain or
|
||||||
|
// in the path, separate HttpKeyBuilders must be used:
|
||||||
|
// host_pattern: 'example.com' path_pattern: '/{id}/{object}/**'
|
||||||
|
// host_pattern: '{id}.example.com' path_pattern: '/{object}/**'
|
||||||
|
// If the host is exactly 'example.com', the first path segment will be used as
|
||||||
|
// the id and the second segment as the object. If the host has a subdomain, the
|
||||||
|
// subdomain will be used as the id and the first segment as the object. If
|
||||||
|
// neither pattern matches, no keys will be extracted.
|
||||||
|
message HttpKeyBuilder {
|
||||||
|
// host_pattern is an ordered list of host template patterns for the desired
|
||||||
|
// value. If any host_pattern values are specified, then at least one must
|
||||||
|
// match, and the last one wins and sets any specified variables. A host
|
||||||
|
// consists of labels separated by dots. Each label is matched against the
|
||||||
|
// label in the pattern as follows:
|
||||||
|
// - "*": Matches any single label.
|
||||||
|
// - "**": Matches zero or more labels (first or last part of host only).
|
||||||
|
// - "{<name>=...}": One or more label capture, where "..." can be any
|
||||||
|
// template that does not include a capture.
|
||||||
|
// - "{<name>}": A single label capture. Identical to {<name>=*}.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// - "example.com": Only applies to the exact host example.com.
|
||||||
|
// - "*.example.com": Matches subdomains of example.com.
|
||||||
|
// - "**.example.com": matches example.com, and all levels of subdomains.
|
||||||
|
// - "{project}.example.com": Extracts the third level subdomain.
|
||||||
|
// - "{project=**}.example.com": Extracts the third level+ subdomains.
|
||||||
|
// - "{project=**}": Extracts the entire host.
|
||||||
|
repeated string host_patterns = 1;
|
||||||
|
|
||||||
|
// path_pattern is an ordered list of path template patterns for the desired
|
||||||
|
// value. If any path_pattern values are specified, then at least one must
|
||||||
|
// match, and the last one wins and sets any specified variables. A path
|
||||||
|
// consists of segments separated by slashes. Each segment is matched against
|
||||||
|
// the segment in the pattern as follows:
|
||||||
|
// - "*": Matches any single segment.
|
||||||
|
// - "**": Matches zero or more segments (first or last part of path only).
|
||||||
|
// - "{<name>=...}": One or more segment capture, where "..." can be any
|
||||||
|
// template that does not include a capture.
|
||||||
|
// - "{<name>}": A single segment capture. Identical to {<name>=*}.
|
||||||
|
// A custom method may also be specified by appending ":" and the custom
|
||||||
|
// method name or "*" to indicate any custom method (including no custom
|
||||||
|
// method). For example, "/*/projects/{project_id}/**:*" extracts
|
||||||
|
// `{project_id}` for any version, resource and custom method that includes
|
||||||
|
// it. By default, any custom method will be matched.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// - "/v1/{name=messages/*}": extracts a name like "messages/12345".
|
||||||
|
// - "/v1/messages/{message_id}": extracts a message_id like "12345".
|
||||||
|
// - "/v1/users/{user_id}/messages/{message_id}": extracts two key values.
|
||||||
|
repeated string path_patterns = 2;
|
||||||
|
|
||||||
|
// List of query parameter names to try to match.
|
||||||
|
// For example: ["parent", "name", "resource.name"]
|
||||||
|
// We extract all the specified query_parameters (case-sensitively). If any
|
||||||
|
// are marked as "required_match" and are not present, this keybuilder fails
|
||||||
|
// to match. If a given parameter appears multiple times (?foo=a&foo=b) we
|
||||||
|
// will report it as a comma-separated string (foo=a,b).
|
||||||
|
repeated NameMatcher query_parameters = 3;
|
||||||
|
|
||||||
|
// List of headers to try to match.
|
||||||
|
// We extract all the specified header values (case-insensitively). If any
|
||||||
|
// are marked as "required_match" and are not present, this keybuilder fails
|
||||||
|
// to match. If a given header appears multiple times in the request we will
|
||||||
|
// report it as a comma-separated string, in standard HTTP fashion.
|
||||||
|
repeated NameMatcher headers = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RouteLookupConfig {
|
||||||
|
// Ordered specifications for constructing keys for HTTP requests. Last
|
||||||
|
// match wins. If no HttpKeyBuilder matches, an empty key_map will be sent to
|
||||||
|
// the lookup service; it should likely reply with a global default route
|
||||||
|
// and raise an alert.
|
||||||
|
repeated HttpKeyBuilder http_keybuilders = 1;
|
||||||
|
|
||||||
|
// Unordered specifications for constructing keys for gRPC requests. All
|
||||||
|
// GrpcKeyBuilders on this list must have unique "name" fields so that the
|
||||||
|
// client is free to prebuild a hash map keyed by name. If no GrpcKeyBuilder
|
||||||
|
// matches, an empty key_map will be sent to the lookup service; it should
|
||||||
|
// likely reply with a global default route and raise an alert.
|
||||||
|
repeated GrpcKeyBuilder grpc_keybuilders = 2;
|
||||||
|
|
||||||
|
// The name of the lookup service as a gRPC URI. Typically, this will be
|
||||||
|
// a subdomain of the target, such as "lookup.datastore.googleapis.com".
|
||||||
|
string lookup_service = 3;
|
||||||
|
|
||||||
|
// Configure a timeout value for lookup service requests.
|
||||||
|
// Defaults to 10 seconds if not specified.
|
||||||
|
google.protobuf.Duration lookup_service_timeout = 4;
|
||||||
|
|
||||||
|
// How long are responses valid for (like HTTP Cache-Control).
|
||||||
|
// If omitted (i.e. 0), a default value of 5 minutes will be used.
|
||||||
|
// This value is clamped to 5 minutes to avoid unflushable bad responses.
|
||||||
|
google.protobuf.Duration max_age = 5;
|
||||||
|
|
||||||
|
// After a response has been in the client cache for this amount of time
|
||||||
|
// and is re-requested, start an asynchronous RPC to re-validate it.
|
||||||
|
// This value should be less than max_age by at least the length of a
|
||||||
|
// typical RTT to the Route Lookup Service to fully mask the RTT latency.
|
||||||
|
// If omitted, keys are only re-requested after they have expired.
|
||||||
|
google.protobuf.Duration stale_age = 6;
|
||||||
|
|
||||||
|
// Rough indicator of amount of memory to use for the client cache. Some of
|
||||||
|
// the data structure overhead is not accounted for, so actual memory consumed
|
||||||
|
// will be somewhat greater than this value. If this field is omitted or set
|
||||||
|
// to zero, a client default will be used. The value may be capped to a lower
|
||||||
|
// amount based on client configuration.
|
||||||
|
int64 cache_size_bytes = 7;
|
||||||
|
|
||||||
|
// This is a list of all the possible targets that can be returned by the
|
||||||
|
// lookup service. If a target not on this list is returned, it will be
|
||||||
|
// treated the same as an RPC error from the RLS.
|
||||||
|
repeated string valid_targets = 8;
|
||||||
|
|
||||||
|
// This value provides a default target to use if needed. It will be used for
|
||||||
|
// request processing strategy SYNC_LOOKUP_DEFAULT_TARGET_ON_ERROR if RLS
|
||||||
|
// returns an error, or strategy ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS if RLS
|
||||||
|
// returns an error or there is a cache miss in the client. It will also be
|
||||||
|
// used if there are no healthy backends for an RLS target. Note that
|
||||||
|
// requests can be routed only to a subdomain of the original target,
|
||||||
|
// e.g. "us_east_1.cloudbigtable.googleapis.com".
|
||||||
|
string default_target = 9;
|
||||||
|
|
||||||
|
// Specify how to process a request when not already in the cache.
|
||||||
|
enum RequestProcessingStrategy {
|
||||||
|
STRATEGY_UNSPECIFIED = 0;
|
||||||
|
|
||||||
|
// Query the RLS and process the request using target returned by the
|
||||||
|
// lookup. The target will then be cached and used for processing
|
||||||
|
// subsequent requests for the same key. Any errors during lookup service
|
||||||
|
// processing will fall back to default target for request processing.
|
||||||
|
SYNC_LOOKUP_DEFAULT_TARGET_ON_ERROR = 1;
|
||||||
|
|
||||||
|
// Query the RLS and process the request using target returned by the
|
||||||
|
// lookup. The target will then be cached and used for processing
|
||||||
|
// subsequent requests for the same key. Any errors during lookup service
|
||||||
|
// processing will return an error back to the client. Services with
|
||||||
|
// strict regional routing requirements should use this strategy.
|
||||||
|
SYNC_LOOKUP_CLIENT_SEES_ERROR = 2;
|
||||||
|
|
||||||
|
// Query the RLS asynchronously but respond with the default target. The
|
||||||
|
// target in the lookup response will then be cached and used for
|
||||||
|
// subsequent requests. Services with strict latency requirements (but not
|
||||||
|
// strict regional routing requirements) should use this strategy.
|
||||||
|
ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS = 3;
|
||||||
|
}
|
||||||
|
RequestProcessingStrategy request_processing_strategy = 10;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.rls.internal;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import com.google.common.base.Converter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.grpc.internal.JsonParser;
|
||||||
|
import io.grpc.lookup.v1.RouteLookupRequest;
|
||||||
|
import io.grpc.lookup.v1.RouteLookupResponse;
|
||||||
|
import io.grpc.rls.internal.RlsProtoConverters.RouteLookupConfigConverter;
|
||||||
|
import io.grpc.rls.internal.RlsProtoConverters.RouteLookupRequestConverter;
|
||||||
|
import io.grpc.rls.internal.RlsProtoConverters.RouteLookupResponseConverter;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.GrpcKeyBuilder;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.GrpcKeyBuilder.Name;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.NameMatcher;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.RequestProcessingStrategy;
|
||||||
|
import io.grpc.rls.internal.RlsProtoData.RouteLookupConfig;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class RlsProtoConvertersTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_toRequestProto() {
|
||||||
|
Converter<RouteLookupRequest, RlsProtoData.RouteLookupRequest> converter =
|
||||||
|
new RouteLookupRequestConverter();
|
||||||
|
RouteLookupRequest proto = RouteLookupRequest.newBuilder()
|
||||||
|
.setServer("server")
|
||||||
|
.setPath("path")
|
||||||
|
.setTargetType("target")
|
||||||
|
.putKeyMap("key1", "val1")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RlsProtoData.RouteLookupRequest object = converter.convert(proto);
|
||||||
|
|
||||||
|
assertThat(object.getServer()).isEqualTo("server");
|
||||||
|
assertThat(object.getPath()).isEqualTo("path");
|
||||||
|
assertThat(object.getTargetType()).isEqualTo("target");
|
||||||
|
assertThat(object.getKeyMap()).containsExactly("key1", "val1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_toRequestObject() {
|
||||||
|
Converter<RlsProtoData.RouteLookupRequest, RouteLookupRequest> converter =
|
||||||
|
new RouteLookupRequestConverter().reverse();
|
||||||
|
RlsProtoData.RouteLookupRequest requestObject =
|
||||||
|
new RlsProtoData.RouteLookupRequest(
|
||||||
|
"server", "path", "target", ImmutableMap.of("key1", "val1"));
|
||||||
|
|
||||||
|
RouteLookupRequest proto = converter.convert(requestObject);
|
||||||
|
|
||||||
|
assertThat(proto.getServer()).isEqualTo("server");
|
||||||
|
assertThat(proto.getPath()).isEqualTo("path");
|
||||||
|
assertThat(proto.getTargetType()).isEqualTo("target");
|
||||||
|
assertThat(proto.getKeyMapMap()).containsExactly("key1", "val1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_toResponseProto() {
|
||||||
|
Converter<RouteLookupResponse, RlsProtoData.RouteLookupResponse> converter =
|
||||||
|
new RouteLookupResponseConverter();
|
||||||
|
RouteLookupResponse proto = RouteLookupResponse.newBuilder()
|
||||||
|
.setTarget("target")
|
||||||
|
.setHeaderData("some header data")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RlsProtoData.RouteLookupResponse object = converter.convert(proto);
|
||||||
|
|
||||||
|
assertThat(object.getTarget()).isEqualTo("target");
|
||||||
|
assertThat(object.getHeaderData()).isEqualTo("some header data");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_toResponseObject() {
|
||||||
|
Converter<RlsProtoData.RouteLookupResponse, RouteLookupResponse> converter =
|
||||||
|
new RouteLookupResponseConverter().reverse();
|
||||||
|
|
||||||
|
RlsProtoData.RouteLookupResponse object =
|
||||||
|
new RlsProtoData.RouteLookupResponse("target", "some header data");
|
||||||
|
|
||||||
|
RouteLookupResponse proto = converter.convert(object);
|
||||||
|
|
||||||
|
assertThat(proto.getTarget()).isEqualTo("target");
|
||||||
|
assertThat(proto.getHeaderData()).isEqualTo("some header data");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convert_jsonRlsConfig() throws IOException {
|
||||||
|
String jsonStr = "{\n"
|
||||||
|
+ " \"grpcKeyBuilders\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"names\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"service\": \"service1\",\n"
|
||||||
|
+ " \"method\": \"create\"\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ " \"headers\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"key\": \"user\","
|
||||||
|
+ " \"names\": [\"User\", \"Parent\"],\n"
|
||||||
|
+ " \"optional\": true\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"key\": \"id\","
|
||||||
|
+ " \"names\": [\"X-Google-Id\"],\n"
|
||||||
|
+ " \"optional\": true\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ]\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"names\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"service\": \"service1\",\n"
|
||||||
|
+ " \"method\": \"*\"\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ " \"headers\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"key\": \"user\","
|
||||||
|
+ " \"names\": [\"User\", \"Parent\"],\n"
|
||||||
|
+ " \"optional\": true\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"key\": \"password\","
|
||||||
|
+ " \"names\": [\"Password\"],\n"
|
||||||
|
+ " \"optional\": true\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ]\n"
|
||||||
|
+ " },\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"names\": [\n"
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"service\": \"service3\",\n"
|
||||||
|
+ " \"method\": \"*\"\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ " \"headers\": ["
|
||||||
|
+ " {\n"
|
||||||
|
+ " \"key\": \"user\","
|
||||||
|
+ " \"names\": [\"User\", \"Parent\"],\n"
|
||||||
|
+ " \"optional\": true\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ]\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " ],\n"
|
||||||
|
+ " \"lookupService\": \"service1\",\n"
|
||||||
|
+ " \"lookupServiceTimeout\": 2,\n"
|
||||||
|
+ " \"maxAge\": 300,\n"
|
||||||
|
+ " \"staleAge\": 240,\n"
|
||||||
|
+ " \"validTargets\": [\"a valid target\"],"
|
||||||
|
+ " \"cacheSizeBytes\": 1000,\n"
|
||||||
|
+ " \"defaultTarget\": \"us_east_1.cloudbigtable.googleapis.com\",\n"
|
||||||
|
+ " \"requestProcessingStrategy\": \"ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS\"\n"
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
RouteLookupConfig expectedConfig =
|
||||||
|
new RouteLookupConfig(
|
||||||
|
ImmutableList.of(
|
||||||
|
new GrpcKeyBuilder(
|
||||||
|
ImmutableList.of(new Name("service1", "create")),
|
||||||
|
ImmutableList.of(
|
||||||
|
new NameMatcher("user", ImmutableList.of("User", "Parent"), true),
|
||||||
|
new NameMatcher("id", ImmutableList.of("X-Google-Id"), true))),
|
||||||
|
new GrpcKeyBuilder(
|
||||||
|
ImmutableList.of(new Name("service1")),
|
||||||
|
ImmutableList.of(
|
||||||
|
new NameMatcher("user", ImmutableList.of("User", "Parent"), true),
|
||||||
|
new NameMatcher("password", ImmutableList.of("Password"), true))),
|
||||||
|
new GrpcKeyBuilder(
|
||||||
|
ImmutableList.of(new Name("service3")),
|
||||||
|
ImmutableList.of(
|
||||||
|
new NameMatcher("user", ImmutableList.of("User", "Parent"), true)))),
|
||||||
|
/* lookupService= */ "service1",
|
||||||
|
/* lookupServiceTimeoutInMillis= */ TimeUnit.SECONDS.toMillis(2),
|
||||||
|
/* maxAgeInMillis= */ TimeUnit.SECONDS.toMillis(300),
|
||||||
|
/* staleAgeInMillis= */ TimeUnit.SECONDS.toMillis(240),
|
||||||
|
/* cacheSize= */ 1000,
|
||||||
|
/* validTargets= */ ImmutableList.of("a valid target"),
|
||||||
|
/* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com",
|
||||||
|
RequestProcessingStrategy.ASYNC_LOOKUP_DEFAULT_TARGET_ON_MISS);
|
||||||
|
|
||||||
|
RouteLookupConfigConverter converter = new RouteLookupConfigConverter();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> parsedJson = (Map<String, ?>) JsonParser.parse(jsonStr);
|
||||||
|
RouteLookupConfig converted = converter.convert(parsedJson);
|
||||||
|
assertThat(converted).isEqualTo(expectedConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,7 @@ include ":grpc-benchmarks"
|
||||||
include ":grpc-services"
|
include ":grpc-services"
|
||||||
include ":grpc-xds"
|
include ":grpc-xds"
|
||||||
include ":grpc-bom"
|
include ":grpc-bom"
|
||||||
|
include ":grpc-rls"
|
||||||
|
|
||||||
project(':grpc-api').projectDir = "$rootDir/api" as File
|
project(':grpc-api').projectDir = "$rootDir/api" as File
|
||||||
project(':grpc-core').projectDir = "$rootDir/core" as File
|
project(':grpc-core').projectDir = "$rootDir/core" as File
|
||||||
|
|
@ -70,6 +71,7 @@ project(':grpc-benchmarks').projectDir = "$rootDir/benchmarks" as File
|
||||||
project(':grpc-services').projectDir = "$rootDir/services" as File
|
project(':grpc-services').projectDir = "$rootDir/services" as File
|
||||||
project(':grpc-xds').projectDir = "$rootDir/xds" as File
|
project(':grpc-xds').projectDir = "$rootDir/xds" as File
|
||||||
project(':grpc-bom').projectDir = "$rootDir/bom" as File
|
project(':grpc-bom').projectDir = "$rootDir/bom" as File
|
||||||
|
project(':grpc-rls').projectDir = "$rootDir/rls" as File
|
||||||
|
|
||||||
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
|
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
|
||||||
println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'
|
println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue