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 {
|
||||
// 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 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"
|
||||
|
||||
# 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
|
||||
[ -f "$base/$proto" ] && cp "$base/$proto" "$project/src/main/proto/$proto"
|
||||
echo "$proto"
|
||||
|
|
|
|||
|
|
@ -129,6 +129,24 @@ public class JsonUtil {
|
|||
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.
|
||||
* 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-xds"
|
||||
include ":grpc-bom"
|
||||
include ":grpc-rls"
|
||||
|
||||
project(':grpc-api').projectDir = "$rootDir/api" 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-xds').projectDir = "$rootDir/xds" as File
|
||||
project(':grpc-bom').projectDir = "$rootDir/bom" as File
|
||||
project(':grpc-rls').projectDir = "$rootDir/rls" as File
|
||||
|
||||
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
|
||||
println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'
|
||||
|
|
|
|||
Loading…
Reference in New Issue