Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions xds/src/main/java/io/grpc/xds/EnvoyProtoData.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -1118,24 +1117,23 @@ static StructOrError<HeaderMatcher> convertEnvoyProtoHeaderMatcher(
* See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.route.v3.RouteAction}.
*/
static final class RouteAction {
private final long timeoutNano;
@Nullable
private final Long timeoutNano;
// Exactly one of the following fields is non-null.
@Nullable
private final String cluster;
@Nullable
private final List<ClusterWeight> weightedClusters;

@VisibleForTesting
RouteAction(
long timeoutNano,
@Nullable String cluster,
RouteAction(@Nullable Long timeoutNano, @Nullable String cluster,
@Nullable List<ClusterWeight> weightedClusters) {
this.timeoutNano = timeoutNano;
this.cluster = cluster;
this.weightedClusters = weightedClusters;
}


@Nullable
Long getTimeoutNano() {
return timeoutNano;
}
Expand Down Expand Up @@ -1172,7 +1170,9 @@ public int hashCode() {
@Override
public String toString() {
ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
toStringHelper.add("timeout", timeoutNano + "ns");
if (timeoutNano != null) {
toStringHelper.add("timeout", timeoutNano + "ns");
}
if (cluster != null) {
toStringHelper.add("cluster", cluster);
}
Expand Down Expand Up @@ -1212,14 +1212,15 @@ static StructOrError<RouteAction> fromEnvoyProtoRouteAction(
return StructOrError.fromError(
"Unknown cluster specifier: " + proto.getClusterSpecifierCase());
}
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;
Long timeoutNano = null;
if (proto.hasMaxStreamDuration()) {
io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration
= proto.getMaxStreamDuration();
if (maxStreamDuration.hasGrpcTimeoutHeaderMax()) {
timeoutNano = Durations.toNanos(maxStreamDuration.getGrpcTimeoutHeaderMax());
} else if (maxStreamDuration.hasMaxStreamDuration()) {
timeoutNano = Durations.toNanos(maxStreamDuration.getMaxStreamDuration());
}
}
return StructOrError.fromStruct(new RouteAction(timeoutNano, cluster, weightedClusters));
}
Expand Down
34 changes: 29 additions & 5 deletions xds/src/main/java/io/grpc/xds/XdsNameResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ final class XdsNameResolver extends NameResolver {
private final ConcurrentMap<String, AtomicInteger> clusterRefs = new ConcurrentHashMap<>();
private final ConfigSelector configSelector = new ConfigSelector();

private volatile List<Route> currentRoutes = Collections.emptyList();
private volatile RoutingConfig routingConfig = RoutingConfig.empty;
private Listener2 listener;
private ObjectPool<XdsClient> xdsClientPool;
private XdsClient xdsClient;
Expand Down Expand Up @@ -326,7 +326,7 @@ public Result selectConfig(PickSubchannelArgs args) {
String cluster = null;
Route selectedRoute = null;
do {
for (Route route : currentRoutes) {
for (Route route : routingConfig.routes) {
if (route.getRouteMatch().matches(
"/" + args.getMethodDescriptor().getFullMethodName(), asciiHeaders)) {
selectedRoute = route;
Expand Down Expand Up @@ -359,8 +359,13 @@ public Result selectConfig(PickSubchannelArgs args) {
// TODO(chengyuanzhang): avoid service config generation and parsing for each call.
Map<String, ?> rawServiceConfig = Collections.emptyMap();
if (enableTimeout) {
rawServiceConfig = generateServiceConfigWithMethodTimeoutConfig(
selectedRoute.getRouteAction().getTimeoutNano());
Long timeoutNano = selectedRoute.getRouteAction().getTimeoutNano();
if (timeoutNano == null) {
timeoutNano = routingConfig.fallbackTimeoutNano;
}
if (timeoutNano > 0) {
rawServiceConfig = generateServiceConfigWithMethodTimeoutConfig(timeoutNano);
}
}
ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig);
Object config = parsedServiceConfig.getConfig();
Expand Down Expand Up @@ -430,10 +435,12 @@ private class ResolveState implements LdsResourceWatcher {
private String rdsResource;
@Nullable
private RdsResourceWatcher rdsWatcher;
private long httpMaxStreamDurationNano;

@Override
public void onChanged(LdsUpdate update) {
logger.log(XdsLogLevel.INFO, "Receive LDS resource update: {0}", update);
httpMaxStreamDurationNano = update.getHttpMaxStreamDurationNano();
List<VirtualHost> virtualHosts = update.getVirtualHosts();
String rdsName = update.getRdsName();
if (rdsName != null && rdsName.equals(rdsResource)) {
Expand Down Expand Up @@ -479,6 +486,8 @@ private void stop() {
private void updateRoutes(List<VirtualHost> virtualHosts) {
VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, authority);
if (virtualHost == null) {
logger.log(XdsLogLevel.WARNING,
"Failed to find virtual host matching hostname {0}", authority);
listener.onResult(emptyResult);
return;
}
Expand Down Expand Up @@ -515,7 +524,7 @@ private void updateRoutes(List<VirtualHost> virtualHosts) {
}
// Make newly added clusters selectable by config selector and deleted clusters no longer
// selectable.
currentRoutes = routes;
routingConfig = new RoutingConfig(httpMaxStreamDurationNano, routes);
shouldUpdateResult = false;
for (String cluster : deletedClusters) {
int count = clusterRefs.get(cluster).decrementAndGet();
Expand Down Expand Up @@ -560,4 +569,19 @@ public void onResourceDoesNotExist(String resourceName) {
}
}
}

/**
* Grouping of the list of usable routes and their corresponding fallback timeout value.
*/
private static class RoutingConfig {
private long fallbackTimeoutNano;
private List<Route> routes;

private static RoutingConfig empty = new RoutingConfig(0L, Collections.<Route>emptyList());

private RoutingConfig(long fallbackTimeoutNano, List<Route> routes) {
this.fallbackTimeoutNano = fallbackTimeoutNano;
this.routes = routes;
}
}
}
117 changes: 64 additions & 53 deletions xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher;
import io.envoyproxy.envoy.config.route.v3.RedirectAction;
import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
Expand Down Expand Up @@ -212,7 +213,7 @@ public void convertRoute() {
new Route(
new RouteMatch(PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), "cluster-foo", null)));
new RouteAction(null, "cluster-foo", null)));

io.envoyproxy.envoy.config.route.v3.Route unsupportedProto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
Expand Down Expand Up @@ -370,74 +371,84 @@ public void convertRouteMatch_withRuntimeFraction() {
}

@Test
public void convertRouteAction() {
// cluster_specifier = cluster, default timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto1 =
public void convertRouteAction_cluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> 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();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().getCluster()).isEqualTo("cluster-foo");
assertThat(struct.getStruct().getWeightedCluster()).isNull();
}

// cluster_specifier = cluster, infinity timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto2 =
@Test
public void convertRouteAction_weightedCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setMaxGrpcTimeout(Durations.fromNanos(0))
.setTimeout(Durations.fromMicros(20L))
.setCluster("cluster-foo")
.setWeightedClusters(
WeightedCluster.newBuilder()
.addClusters(
WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30)))
.addClusters(WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-bar")
.setWeight(UInt32Value.newBuilder().setValue(70))))
.build();
StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2);
assertThat(struct2.getStruct().getTimeoutNano())
.isEqualTo(Long.MAX_VALUE); // infinite
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().getCluster()).isNull();
assertThat(struct.getStruct().getWeightedCluster()).containsExactly(
new ClusterWeight("cluster-foo", 30), new ClusterWeight("cluster-bar", 70));
}

@Test
public void convertRouteAction_unspecifiedClusterError() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.getDefaultInstance();
StructOrError<RouteAction> unsetStruct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(unsetStruct.getStruct()).isNull();
assertThat(unsetStruct.getErrorDetail()).isNotNull();
}

// cluster_specifier = cluster, infinity timeout
io.envoyproxy.envoy.config.route.v3.RouteAction proto3 =
@Test
public void convertRouteAction_timeoutByGrpcTimeoutHeaderMax() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setTimeout(Durations.fromNanos(0))
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
.setMaxStreamDuration(Durations.fromMillis(20L)))
.build();
StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3);
assertThat(struct3.getStruct().getTimeoutNano()).isEqualTo(Long.MAX_VALUE); // infinite
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}

// cluster_specifier = cluster_header
io.envoyproxy.envoy.config.route.v3.RouteAction proto4 =
@Test
public void convertRouteAction_timeoutByMaxStreamDuration() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setClusterHeader("cluster-bar")
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setMaxStreamDuration(Durations.fromSeconds(5L)))
.build();
StructOrError<RouteAction> struct4 = RouteAction.fromEnvoyProtoRouteAction(proto4);
assertThat(struct4).isNull();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}

// cluster_specifier = weighted_cluster
io.envoyproxy.envoy.config.route.v3.RouteAction proto5 =
@Test
public void convertRouteAction_timeoutUnset() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setMaxGrpcTimeout(Durations.fromSeconds(6L))
.setTimeout(Durations.fromMicros(20L))
.setWeightedClusters(
WeightedCluster.newBuilder()
.addClusters(
WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-baz")
.setWeight(UInt32Value.newBuilder().setValue(100))))
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> 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
io.envoyproxy.envoy.config.route.v3.RouteAction unsetProto =
io.envoyproxy.envoy.config.route.v3.RouteAction.getDefaultInstance();
StructOrError<RouteAction> unsetStruct = RouteAction.fromEnvoyProtoRouteAction(unsetProto);
assertThat(unsetStruct.getErrorDetail()).isNotNull();
assertThat(unsetStruct.getStruct()).isNull();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isNull();
}

@Test
Expand Down
Loading