xds: parse timeout from RDS responses (#7257)

This commit is contained in:
Chengyuan Zhang 2020-07-31 19:12:00 +00:00 committed by GitHub
parent 800ef216a8
commit 14af76cab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 19 deletions

View File

@ -26,6 +26,7 @@ import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue; import com.google.protobuf.NullValue;
import com.google.protobuf.Struct; import com.google.protobuf.Struct;
import com.google.protobuf.Value; import com.google.protobuf.Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern; import com.google.re2j.Pattern;
import com.google.re2j.PatternSyntaxException; import com.google.re2j.PatternSyntaxException;
import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.envoyproxy.envoy.type.v3.FractionalPercent;
@ -40,6 +41,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@ -1067,6 +1069,7 @@ final class EnvoyProtoData {
/** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.RouteAction}. */ /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.RouteAction}. */
static final class RouteAction { static final class RouteAction {
private final long timeoutNano;
// Exactly one of the following fields is non-null. // Exactly one of the following fields is non-null.
@Nullable @Nullable
private final String cluster; private final String cluster;
@ -1074,11 +1077,20 @@ final class EnvoyProtoData {
private final List<ClusterWeight> weightedClusters; private final List<ClusterWeight> weightedClusters;
@VisibleForTesting @VisibleForTesting
RouteAction(@Nullable String cluster, @Nullable List<ClusterWeight> weightedClusters) { RouteAction(
long timeoutNano,
@Nullable String cluster,
@Nullable List<ClusterWeight> weightedClusters) {
this.timeoutNano = timeoutNano;
this.cluster = cluster; this.cluster = cluster;
this.weightedClusters = weightedClusters; this.weightedClusters = weightedClusters;
} }
Long getTimeoutNano() {
return timeoutNano;
}
@Nullable @Nullable
String getCluster() { String getCluster() {
return cluster; return cluster;
@ -1098,18 +1110,20 @@ final class EnvoyProtoData {
return false; return false;
} }
RouteAction that = (RouteAction) o; RouteAction that = (RouteAction) o;
return Objects.equals(cluster, that.cluster) return Objects.equals(timeoutNano, that.timeoutNano)
&& Objects.equals(cluster, that.cluster)
&& Objects.equals(weightedClusters, that.weightedClusters); && Objects.equals(weightedClusters, that.weightedClusters);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(cluster, weightedClusters); return Objects.hash(timeoutNano, cluster, weightedClusters);
} }
@Override @Override
public String toString() { public String toString() {
ToStringHelper toStringHelper = MoreObjects.toStringHelper(this); ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
toStringHelper.add("timeout", timeoutNano + "ns");
if (cluster != null) { if (cluster != null) {
toStringHelper.add("cluster", cluster); toStringHelper.add("cluster", cluster);
} }
@ -1146,7 +1160,16 @@ final class EnvoyProtoData {
return StructOrError.fromError( return StructOrError.fromError(
"Unknown cluster specifier: " + proto.getClusterSpecifierCase()); "Unknown cluster specifier: " + proto.getClusterSpecifierCase());
} }
return StructOrError.fromStruct(new RouteAction(cluster, weightedClusters)); long timeoutNano = TimeUnit.SECONDS.toNanos(15L); // default 15s
if (proto.hasMaxGrpcTimeout()) {
timeoutNano = Durations.toNanos(proto.getMaxGrpcTimeout());
} else if (proto.hasTimeout()) {
timeoutNano = Durations.toNanos(proto.getTimeout());
}
if (timeoutNano == 0) {
timeoutNano = Long.MAX_VALUE;
}
return StructOrError.fromStruct(new RouteAction(timeoutNano, cluster, weightedClusters));
} }
} }

View File

@ -24,6 +24,7 @@ import com.google.protobuf.BoolValue;
import com.google.protobuf.Struct; import com.google.protobuf.Struct;
import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt32Value;
import com.google.protobuf.Value; import com.google.protobuf.Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern; import com.google.re2j.Pattern;
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent; import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher;
@ -44,6 +45,7 @@ import io.grpc.xds.RouteMatch.HeaderMatcher;
import io.grpc.xds.RouteMatch.PathMatcher; import io.grpc.xds.RouteMatch.PathMatcher;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -206,7 +208,7 @@ public class EnvoyProtoDataTest {
new Route( new Route(
new RouteMatch(new PathMatcher("/service/method", null, null), new RouteMatch(new PathMatcher("/service/method", null, null),
Collections.<HeaderMatcher>emptyList(), null), Collections.<HeaderMatcher>emptyList(), null),
new RouteAction("cluster-foo", null))); new RouteAction(TimeUnit.SECONDS.toNanos(15L), "cluster-foo", null)));
io.envoyproxy.envoy.config.route.v3.Route unsupportedProto = io.envoyproxy.envoy.config.route.v3.Route unsupportedProto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder() io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
@ -393,27 +395,51 @@ public class EnvoyProtoDataTest {
@Test @Test
public void convertRouteAction() { public void convertRouteAction() {
// cluster_specifier = cluster // cluster_specifier = cluster, default timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto1 = io.envoyproxy.envoy.config.route.v3.RouteAction proto1 =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo") .setCluster("cluster-foo")
.build(); .build();
StructOrError<RouteAction> struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1); StructOrError<RouteAction> struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1);
assertThat(struct1.getErrorDetail()).isNull(); assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct().getTimeoutNano())
.isEqualTo(TimeUnit.SECONDS.toNanos(15L)); // default value
assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo"); assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo");
assertThat(struct1.getStruct().getWeightedCluster()).isNull(); assertThat(struct1.getStruct().getWeightedCluster()).isNull();
// cluster_specifier = cluster_header // cluster_specifier = cluster, infinity timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto2 = io.envoyproxy.envoy.config.route.v3.RouteAction proto2 =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setMaxGrpcTimeout(Durations.fromNanos(0))
.setTimeout(Durations.fromMicros(20L))
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2);
assertThat(struct2.getStruct().getTimeoutNano())
.isEqualTo(Long.MAX_VALUE); // infinite
// cluster_specifier = cluster, infinity timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto3 =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setTimeout(Durations.fromNanos(0))
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3);
assertThat(struct3.getStruct().getTimeoutNano()).isEqualTo(Long.MAX_VALUE); // infinite
// cluster_specifier = cluster_header
io.envoyproxy.envoy.config.route.v3.RouteAction proto4 =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setClusterHeader("cluster-bar") .setClusterHeader("cluster-bar")
.build(); .build();
StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2); StructOrError<RouteAction> struct4 = RouteAction.fromEnvoyProtoRouteAction(proto4);
assertThat(struct2).isNull(); assertThat(struct4).isNull();
// cluster_specifier = weighted_cluster // cluster_specifier = weighted_cluster
io.envoyproxy.envoy.config.route.v3.RouteAction proto3 = io.envoyproxy.envoy.config.route.v3.RouteAction proto5 =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setMaxGrpcTimeout(Durations.fromSeconds(6L))
.setTimeout(Durations.fromMicros(20L))
.setWeightedClusters( .setWeightedClusters(
WeightedCluster.newBuilder() WeightedCluster.newBuilder()
.addClusters( .addClusters(
@ -422,10 +448,12 @@ public class EnvoyProtoDataTest {
.setName("cluster-baz") .setName("cluster-baz")
.setWeight(UInt32Value.newBuilder().setValue(100)))) .setWeight(UInt32Value.newBuilder().setValue(100))))
.build(); .build();
StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3); StructOrError<RouteAction> struct5 = RouteAction.fromEnvoyProtoRouteAction(proto5);
assertThat(struct3.getErrorDetail()).isNull(); assertThat(struct5.getErrorDetail()).isNull();
assertThat(struct3.getStruct().getCluster()).isNull(); assertThat(struct5.getStruct().getTimeoutNano())
assertThat(struct3.getStruct().getWeightedCluster()) .isEqualTo(TimeUnit.SECONDS.toNanos(6L));
assertThat(struct5.getStruct().getCluster()).isNull();
assertThat(struct5.getStruct().getWeightedCluster())
.containsExactly(new ClusterWeight("cluster-baz", 100)); .containsExactly(new ClusterWeight("cluster-baz", 100));
// cluster_specifier unset // cluster_specifier unset

View File

@ -750,7 +750,8 @@ public class XdsClientImplTest {
new io.grpc.xds.RouteMatch( new io.grpc.xds.RouteMatch(
/* prefix= */ null, /* prefix= */ null,
/* path= */ "/service1/method1"), /* path= */ "/service1/method1"),
new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); new EnvoyProtoData.RouteAction(
TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null)));
assertThat(routes.get(1)).isEqualTo( assertThat(routes.get(1)).isEqualTo(
new EnvoyProtoData.Route( new EnvoyProtoData.Route(
// path match with weighted cluster route // path match with weighted cluster route
@ -758,6 +759,7 @@ public class XdsClientImplTest {
/* prefix= */ null, /* prefix= */ null,
/* path= */ "/service2/method2"), /* path= */ "/service2/method2"),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction(
TimeUnit.SECONDS.toNanos(15L),
null, null,
ImmutableList.of( ImmutableList.of(
new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30),
@ -769,7 +771,8 @@ public class XdsClientImplTest {
new io.grpc.xds.RouteMatch( new io.grpc.xds.RouteMatch(
/* prefix= */ "/service1/", /* prefix= */ "/service1/",
/* path= */ null), /* path= */ null),
new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); new EnvoyProtoData.RouteAction(
TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null)));
assertThat(routes.get(3)).isEqualTo( assertThat(routes.get(3)).isEqualTo(
new EnvoyProtoData.Route( new EnvoyProtoData.Route(
// default match with cluster route // default match with cluster route
@ -777,7 +780,7 @@ public class XdsClientImplTest {
/* prefix= */ "", /* prefix= */ "",
/* path= */ null), /* path= */ null),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction(
"cluster.googleapis.com", null))); TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null)));
} }
/** /**

View File

@ -83,7 +83,7 @@ public class XdsNameResolverTest {
new RouteMatch( new RouteMatch(
new PathMatcher(null, "", null), Collections.<HeaderMatcher>emptyList(), new PathMatcher(null, "", null), Collections.<HeaderMatcher>emptyList(),
new FractionMatcher(10, 20)), new FractionMatcher(10, 20)),
new RouteAction("cluster-foo", null)); new RouteAction(15L, "cluster-foo", null));
Route r2 = Route r2 =
new Route( new Route(
new RouteMatch( new RouteMatch(
@ -92,6 +92,7 @@ public class XdsNameResolverTest {
new HeaderMatcher(":scheme", "https", null, null, null, null, null, false)), new HeaderMatcher(":scheme", "https", null, null, null, null, null, false)),
null), null),
new RouteAction( new RouteAction(
15L,
null, null,
Arrays.asList( Arrays.asList(
new ClusterWeight("cluster-foo", 20), new ClusterWeight("cluster-foo", 20),
@ -134,7 +135,7 @@ public class XdsNameResolverTest {
new RouteMatch( new RouteMatch(
new PathMatcher("/service/method", null, null), new PathMatcher("/service/method", null, null),
Collections.<HeaderMatcher>emptyList(), null), Collections.<HeaderMatcher>emptyList(), null),
new RouteAction("cluster-foo", null)); new RouteAction(15L, "cluster-foo", null));
Map<String, ?> config = Map<String, ?> config =
XdsNameResolver.generateXdsRoutingRawConfig(Arrays.asList(route, route)); XdsNameResolver.generateXdsRoutingRawConfig(Arrays.asList(route, route));