diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index eeae2f9617..14a62f29f5 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -26,6 +26,7 @@ import com.google.protobuf.ListValue; import com.google.protobuf.NullValue; import com.google.protobuf.Struct; import com.google.protobuf.Value; +import com.google.protobuf.util.Durations; import com.google.re2j.Pattern; import com.google.re2j.PatternSyntaxException; import io.envoyproxy.envoy.type.v3.FractionalPercent; @@ -40,6 +41,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** @@ -1067,6 +1069,7 @@ final class EnvoyProtoData { /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.RouteAction}. */ static final class RouteAction { + private final long timeoutNano; // Exactly one of the following fields is non-null. @Nullable private final String cluster; @@ -1074,11 +1077,20 @@ final class EnvoyProtoData { private final List weightedClusters; @VisibleForTesting - RouteAction(@Nullable String cluster, @Nullable List weightedClusters) { + RouteAction( + long timeoutNano, + @Nullable String cluster, + @Nullable List weightedClusters) { + this.timeoutNano = timeoutNano; this.cluster = cluster; this.weightedClusters = weightedClusters; } + + Long getTimeoutNano() { + return timeoutNano; + } + @Nullable String getCluster() { return cluster; @@ -1098,18 +1110,20 @@ final class EnvoyProtoData { return false; } 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); } @Override public int hashCode() { - return Objects.hash(cluster, weightedClusters); + return Objects.hash(timeoutNano, cluster, weightedClusters); } @Override public String toString() { ToStringHelper toStringHelper = MoreObjects.toStringHelper(this); + toStringHelper.add("timeout", timeoutNano + "ns"); if (cluster != null) { toStringHelper.add("cluster", cluster); } @@ -1146,7 +1160,16 @@ final class EnvoyProtoData { return StructOrError.fromError( "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)); } } diff --git a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java index fa278fb814..24e739bf56 100644 --- a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java +++ b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java @@ -24,6 +24,7 @@ import com.google.protobuf.BoolValue; import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; import com.google.protobuf.Value; +import com.google.protobuf.util.Durations; import com.google.re2j.Pattern; import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent; 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 java.util.Arrays; import java.util.Collections; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -206,7 +208,7 @@ public class EnvoyProtoDataTest { new Route( new RouteMatch(new PathMatcher("/service/method", null, null), Collections.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.newBuilder() @@ -393,27 +395,51 @@ public class EnvoyProtoDataTest { @Test 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.newBuilder() .setCluster("cluster-foo") .build(); StructOrError struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1); 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().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.newBuilder() + .setMaxGrpcTimeout(Durations.fromNanos(0)) + .setTimeout(Durations.fromMicros(20L)) + .setCluster("cluster-foo") + .build(); + StructOrError 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 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() .setClusterHeader("cluster-bar") .build(); - StructOrError struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2); - assertThat(struct2).isNull(); + StructOrError struct4 = RouteAction.fromEnvoyProtoRouteAction(proto4); + assertThat(struct4).isNull(); // 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() + .setMaxGrpcTimeout(Durations.fromSeconds(6L)) + .setTimeout(Durations.fromMicros(20L)) .setWeightedClusters( WeightedCluster.newBuilder() .addClusters( @@ -422,10 +448,12 @@ public class EnvoyProtoDataTest { .setName("cluster-baz") .setWeight(UInt32Value.newBuilder().setValue(100)))) .build(); - StructOrError struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3); - assertThat(struct3.getErrorDetail()).isNull(); - assertThat(struct3.getStruct().getCluster()).isNull(); - assertThat(struct3.getStruct().getWeightedCluster()) + StructOrError struct5 = RouteAction.fromEnvoyProtoRouteAction(proto5); + assertThat(struct5.getErrorDetail()).isNull(); + assertThat(struct5.getStruct().getTimeoutNano()) + .isEqualTo(TimeUnit.SECONDS.toNanos(6L)); + assertThat(struct5.getStruct().getCluster()).isNull(); + assertThat(struct5.getStruct().getWeightedCluster()) .containsExactly(new ClusterWeight("cluster-baz", 100)); // cluster_specifier unset diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 19c3ec30be..a771d9fb7d 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -750,7 +750,8 @@ public class XdsClientImplTest { new io.grpc.xds.RouteMatch( /* prefix= */ null, /* 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( new EnvoyProtoData.Route( // path match with weighted cluster route @@ -758,6 +759,7 @@ public class XdsClientImplTest { /* prefix= */ null, /* path= */ "/service2/method2"), new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), null, ImmutableList.of( new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), @@ -769,7 +771,8 @@ public class XdsClientImplTest { new io.grpc.xds.RouteMatch( /* prefix= */ "/service1/", /* 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( new EnvoyProtoData.Route( // default match with cluster route @@ -777,7 +780,7 @@ public class XdsClientImplTest { /* prefix= */ "", /* path= */ null), new EnvoyProtoData.RouteAction( - "cluster.googleapis.com", null))); + TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); } /** diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 584a53a8f6..16ce37a6e5 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -83,7 +83,7 @@ public class XdsNameResolverTest { new RouteMatch( new PathMatcher(null, "", null), Collections.emptyList(), new FractionMatcher(10, 20)), - new RouteAction("cluster-foo", null)); + new RouteAction(15L, "cluster-foo", null)); Route r2 = new Route( new RouteMatch( @@ -92,6 +92,7 @@ public class XdsNameResolverTest { new HeaderMatcher(":scheme", "https", null, null, null, null, null, false)), null), new RouteAction( + 15L, null, Arrays.asList( new ClusterWeight("cluster-foo", 20), @@ -134,7 +135,7 @@ public class XdsNameResolverTest { new RouteMatch( new PathMatcher("/service/method", null, null), Collections.emptyList(), null), - new RouteAction("cluster-foo", null)); + new RouteAction(15L, "cluster-foo", null)); Map config = XdsNameResolver.generateXdsRoutingRawConfig(Arrays.asList(route, route));