rls: add proto and converter (#6743)

This commit is contained in:
Jihun Cho 2020-02-28 21:41:19 -08:00 committed by GitHub
parent 37b231348e
commit 0fd4975d4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1597 additions and 2 deletions

View File

@ -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')

View File

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

View File

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

22
rls/build.gradle Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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