Skip to content

Commit

Permalink
rls: overhaul RouteLookupConfig validation (#8645)
Browse files Browse the repository at this point in the history
The `RlsProtoData.RouteLookupConfig` class is out-of-date. 

- Some of the fields were long, but now are of `Duration` type. 
- Some of the fields are deleted. 
- The validation of some of the fields either have been changed or were wrong since beginning.

Now overhaul all the fields in `RlsProtoData.RouteLookupConfig` class based on the spec http://go/grpc-rls-lb-policy-design#heading=h.y3h669gfpown.

Also move the validation logic in json parsing rather than in the constructor of `RouteLookupConfig`.
  • Loading branch information
dapengzhang0 committed Nov 30, 2021
1 parent cb4b914 commit 2330922
Show file tree
Hide file tree
Showing 10 changed files with 527 additions and 287 deletions.
6 changes: 3 additions & 3 deletions rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java
Expand Up @@ -128,9 +128,9 @@ private CachingRlsLbClient(Builder builder) {
synchronizationContext = helper.getSynchronizationContext();
lbPolicyConfig = checkNotNull(builder.lbPolicyConfig, "lbPolicyConfig");
RouteLookupConfig rlsConfig = lbPolicyConfig.getRouteLookupConfig();
maxAgeNanos = TimeUnit.MILLISECONDS.toNanos(rlsConfig.getMaxAgeInMillis());
staleAgeNanos = TimeUnit.MILLISECONDS.toNanos(rlsConfig.getStaleAgeInMillis());
callTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(rlsConfig.getLookupServiceTimeoutInMillis());
maxAgeNanos = rlsConfig.getMaxAgeInNanos();
staleAgeNanos = rlsConfig.getStaleAgeInNanos();
callTimeoutNanos = rlsConfig.getLookupServiceTimeoutInNanos();
timeProvider = checkNotNull(builder.timeProvider, "timeProvider");
throttler = checkNotNull(builder.throttler, "throttler");
linkedHashLruCache =
Expand Down
16 changes: 0 additions & 16 deletions rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java
Expand Up @@ -67,22 +67,6 @@ public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawLoadBalanc
JsonUtil.getString(rawLoadBalancingConfigPolicy, "childPolicyConfigTargetFieldName"),
JsonUtil.checkObjectList(
checkNotNull(JsonUtil.getList(rawLoadBalancingConfigPolicy, "childPolicy"))));
// Checking all valid targets to make sure the config is always valid. This strict check
// prevents child policy to handle invalid child policy.
for (String validTarget : routeLookupConfig.getValidTargets()) {
ConfigOrError childPolicyConfigOrError =
lbPolicy
.getEffectiveLbProvider()
.parseLoadBalancingPolicyConfig(lbPolicy.getEffectiveChildPolicy(validTarget));
if (childPolicyConfigOrError.getError() != null) {
return
ConfigOrError.fromError(
childPolicyConfigOrError
.getError()
.augmentDescription(
"failed to parse childPolicy for validTarget: " + validTarget));
}
}
return ConfigOrError.fromConfig(new LbPolicyConfiguration(routeLookupConfig, lbPolicy));
} catch (Exception e) {
return ConfigOrError.fromError(
Expand Down
120 changes: 80 additions & 40 deletions rls/src/main/java/io/grpc/rls/RlsProtoConverters.java
Expand Up @@ -18,8 +18,12 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.base.Converter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.grpc.internal.JsonUtil;
import io.grpc.lookup.v1.RouteLookupRequest;
Expand All @@ -29,10 +33,13 @@
import io.grpc.rls.RlsProtoData.GrpcKeyBuilder.Name;
import io.grpc.rls.RlsProtoData.NameMatcher;
import io.grpc.rls.RlsProtoData.RouteLookupConfig;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.Set;
import javax.annotation.Nullable;

/**
Expand All @@ -41,6 +48,12 @@
*/
final class RlsProtoConverters {

private static final long MAX_AGE_NANOS = MINUTES.toNanos(5);
private static final long MAX_CACHE_SIZE = 5 * 1024 * 1024; // 5MiB
private static final long DEFAULT_LOOKUP_SERVICE_TIMEOUT = SECONDS.toNanos(10);
private static final ImmutableList<String> EXTRA_KEY_NAMES =
ImmutableList.of("host", "service", "method");

/**
* RouteLookupRequestConverter converts between {@link RouteLookupRequest} and {@link
* RlsProtoData.RouteLookupRequest}.
Expand Down Expand Up @@ -96,31 +109,49 @@ static final class RouteLookupConfigConverter
@Override
protected RouteLookupConfig doForward(Map<String, ?> json) {
List<GrpcKeyBuilder> grpcKeyBuilders =
GrpcKeyBuilderConverter
.covertAll(JsonUtil.checkObjectList(JsonUtil.getList(json, "grpcKeyBuilders")));
GrpcKeyBuilderConverter.covertAll(
checkNotNull(JsonUtil.getListOfObjects(json, "grpcKeyBuilders"), "grpcKeyBuilders"));
checkArgument(!grpcKeyBuilders.isEmpty(), "must have at least one GrpcKeyBuilder");
Set<Name> names = new HashSet<>();
for (GrpcKeyBuilder keyBuilder : grpcKeyBuilders) {
for (Name name : keyBuilder.getNames()) {
checkArgument(names.add(name), "duplicate names in grpc_keybuilders: " + name);
}
}
String lookupService = JsonUtil.getString(json, "lookupService");
long timeout =
TimeUnit.SECONDS.toMillis(
orDefault(
JsonUtil.getNumberAsLong(json, "lookupServiceTimeout"),
0L));
Long maxAge =
convertTimeIfNotNull(
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, JsonUtil.getNumberAsLong(json, "maxAge"));
Long staleAge =
convertTimeIfNotNull(
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, JsonUtil.getNumberAsLong(json, "staleAge"));
long cacheSize = orDefault(JsonUtil.getNumberAsLong(json, "cacheSizeBytes"), Long.MAX_VALUE);
List<String> validTargets = JsonUtil.checkStringList(JsonUtil.getList(json, "validTargets"));
String defaultTarget = JsonUtil.getString(json, "defaultTarget");
checkArgument(!Strings.isNullOrEmpty(lookupService), "lookupService must not be empty");
try {
new URI(lookupService);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(
"The lookupService field is not valid URI: " + lookupService, e);
}
long timeout = orDefault(
JsonUtil.getStringAsDuration(json, "lookupServiceTimeout"),
DEFAULT_LOOKUP_SERVICE_TIMEOUT);
checkArgument(timeout > 0, "lookupServiceTimeout should be positive");
Long maxAge = JsonUtil.getStringAsDuration(json, "maxAge");
Long staleAge = JsonUtil.getStringAsDuration(json, "staleAge");
if (maxAge == null) {
checkArgument(staleAge == null, "to specify staleAge, must have maxAge");
maxAge = MAX_AGE_NANOS;
}
if (staleAge == null) {
staleAge = MAX_AGE_NANOS;
}
maxAge = Math.min(maxAge, MAX_AGE_NANOS);
staleAge = Math.min(staleAge, maxAge);
long cacheSize = orDefault(JsonUtil.getNumberAsLong(json, "cacheSizeBytes"), MAX_CACHE_SIZE);
checkArgument(cacheSize > 0, "cacheSize must be positive");
cacheSize = Math.min(cacheSize, MAX_CACHE_SIZE);
String defaultTarget = Strings.emptyToNull(JsonUtil.getString(json, "defaultTarget"));
return new RouteLookupConfig(
grpcKeyBuilders,
lookupService,
/* lookupServiceTimeoutInMillis= */ timeout,
/* maxAgeInMillis= */ maxAge,
/* staleAgeInMillis= */ staleAge,
/* lookupServiceTimeoutInNanos= */ timeout,
/* maxAgeInNanos= */ maxAge,
/* staleAgeInNanos= */ staleAge,
/* cacheSizeBytes= */ cacheSize,
validTargets,
defaultTarget);
}

Expand All @@ -131,13 +162,6 @@ private static <T> T orDefault(@Nullable T value, T defaultValue) {
return value;
}

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();
Expand All @@ -155,25 +179,29 @@ public static List<GrpcKeyBuilder> covertAll(List<Map<String, ?>> keyBuilders) {

@SuppressWarnings("unchecked")
static GrpcKeyBuilder convert(Map<String, ?> keyBuilder) {
List<Map<String, ?>> rawNames =
JsonUtil.checkObjectList(JsonUtil.getList(keyBuilder, "names"));
List<?> rawRawNames = JsonUtil.getList(keyBuilder, "names");
checkArgument(
rawRawNames != null && !rawRawNames.isEmpty(),
"each keyBuilder must have at least one name");
List<Map<String, ?>> rawNames = JsonUtil.checkObjectList(rawRawNames);
List<Name> names = new ArrayList<>();
for (Map<String, ?> rawName : rawNames) {
names.add(
new Name(
JsonUtil.getString(rawName, "service"), JsonUtil.getString(rawName, "method")));
String serviceName = JsonUtil.getString(rawName, "service");
checkArgument(!Strings.isNullOrEmpty(serviceName), "service must not be empty or null");
names.add(new Name(serviceName, JsonUtil.getString(rawName, "method")));
}
List<?> rawRawHeaders = JsonUtil.getList(keyBuilder, "headers");
List<Map<String, ?>> rawHeaders =
JsonUtil.checkObjectList(JsonUtil.getList(keyBuilder, "headers"));
rawRawHeaders == null
? new ArrayList<Map<String, ?>>() : JsonUtil.checkObjectList(rawRawHeaders);
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"));
Boolean requiredMatch = JsonUtil.getBoolean(rawHeader, "requiredMatch");
checkArgument(
matcher.isOptional(), "NameMatcher for GrpcKeyBuilders shouldn't be required");
requiredMatch == null || !requiredMatch,
"requiredMatch shouldn't be specified for gRPC");
NameMatcher matcher = new NameMatcher(
JsonUtil.getString(rawHeader, "key"), (List<String>) rawHeader.get("names"));
nameMatchers.add(matcher);
}
ExtraKeys extraKeys = ExtraKeys.DEFAULT;
Expand All @@ -188,9 +216,21 @@ static GrpcKeyBuilder convert(Map<String, ?> keyBuilder) {
if (constantKeys == null) {
constantKeys = ImmutableMap.of();
}
checkUniqueKey(nameMatchers, constantKeys.keySet());
return new GrpcKeyBuilder(names, nameMatchers, extraKeys, constantKeys);
}
}

private static void checkUniqueKey(List<NameMatcher> nameMatchers, Set<String> constantKeys) {
Set<String> keys = new HashSet<>(constantKeys);
keys.addAll(EXTRA_KEY_NAMES);
for (NameMatcher nameMatcher : nameMatchers) {
keys.add(nameMatcher.getKey());
}
if (keys.size() != nameMatchers.size() + constantKeys.size() + EXTRA_KEY_NAMES.size()) {
throw new IllegalArgumentException("keys in KeyBuilder must be unique");
}
}

private RlsProtoConverters() {}
}

0 comments on commit 2330922

Please sign in to comment.