Skip to content

Commit

Permalink
xds: change route data validation logic (grpc#7047)
Browse files Browse the repository at this point in the history
Changed the logic of parsing Route to skip Routes with action that specifies cluster_header. Eliminate unnecessary validation logic in XdsClientImpl, of which is already covered in converters.
  • Loading branch information
voidzcy committed May 18, 2020
1 parent 02e3c00 commit a86fc47
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 60 deletions.
30 changes: 9 additions & 21 deletions xds/src/main/java/io/grpc/xds/EnvoyProtoData.java
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@ static StructOrError<Route> fromEnvoyProtoRoute(io.envoyproxy.envoy.api.v2.route
default:
return StructOrError.fromError("Unknown action type: " + proto.getActionCase());
}
if (routeAction == null) {
return null;
}
if (routeAction.getErrorDetail() != null) {
return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail());
Expand Down Expand Up @@ -931,16 +934,11 @@ static final class RouteAction {
@Nullable
private final String cluster;
@Nullable
private final String clusterHeader;
@Nullable
private final List<ClusterWeight> weightedClusters;

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

Expand All @@ -949,11 +947,6 @@ String getCluster() {
return cluster;
}

@Nullable
String getClusterHeader() {
return clusterHeader;
}

@Nullable
List<ClusterWeight> getWeightedCluster() {
return weightedClusters;
Expand All @@ -969,13 +962,12 @@ public boolean equals(Object o) {
}
RouteAction that = (RouteAction) o;
return Objects.equals(cluster, that.cluster)
&& Objects.equals(clusterHeader, that.clusterHeader)
&& Objects.equals(weightedClusters, that.weightedClusters);
&& Objects.equals(weightedClusters, that.weightedClusters);
}

@Override
public int hashCode() {
return Objects.hash(cluster, clusterHeader, weightedClusters);
return Objects.hash(cluster, weightedClusters);
}

@Override
Expand All @@ -984,28 +976,24 @@ public String toString() {
if (cluster != null) {
toStringHelper.add("cluster", cluster);
}
if (clusterHeader != null) {
toStringHelper.add("clusterHeader", clusterHeader);
}
if (weightedClusters != null) {
toStringHelper.add("weightedClusters", weightedClusters);
}
return toStringHelper.toString();
}

@Nullable
@VisibleForTesting
static StructOrError<RouteAction> fromEnvoyProtoRouteAction(
io.envoyproxy.envoy.api.v2.route.RouteAction proto) {
String cluster = null;
String clusterHeader = null;
List<ClusterWeight> weightedClusters = null;
switch (proto.getClusterSpecifierCase()) {
case CLUSTER:
cluster = proto.getCluster();
break;
case CLUSTER_HEADER:
clusterHeader = proto.getClusterHeader();
break;
return null;
case WEIGHTED_CLUSTERS:
List<io.envoyproxy.envoy.api.v2.route.WeightedCluster.ClusterWeight> clusterWeights
= proto.getWeightedClusters().getClustersList();
Expand All @@ -1020,7 +1008,7 @@ static StructOrError<RouteAction> fromEnvoyProtoRouteAction(
return StructOrError.fromError(
"Unknown cluster specifier: " + proto.getClusterSpecifierCase());
}
return StructOrError.fromStruct(new RouteAction(cluster, clusterHeader, weightedClusters));
return StructOrError.fromStruct(new RouteAction(cluster, weightedClusters));
}
}

Expand Down
20 changes: 0 additions & 20 deletions xds/src/main/java/io/grpc/xds/XdsClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -864,30 +864,10 @@ static List<EnvoyProtoData.Route> populateRoutesInVirtualHost(VirtualHost virtua
"Virtual host [" + virtualHost.getName()
+ "] contains non-default route as the last route");
}
// We only validate the default route unless path matching is enabled.
if (!enableExperimentalRouting) {
EnvoyProtoData.Route defaultRoute = Iterables.getLast(routes);
if (defaultRoute.getRouteAction().getCluster() == null) {
throw new InvalidProtoDataException(
"Virtual host [" + virtualHost.getName()
+ "] default route contains no cluster name");
}
return Collections.singletonList(defaultRoute);
}

// We do more validation if path matching is enabled, but whether every single route is
// required to be valid for grpc is TBD.
// For now we consider the whole list invalid if anything invalid for grpc is found.
// TODO(zdapeng): Fix it if the decision is different from current implementation.
// TODO(zdapeng): Add test for validation.
for (EnvoyProtoData.Route route : routes) {
if (route.getRouteAction().getCluster() == null
&& route.getRouteAction().getWeightedCluster() == null) {
throw new InvalidProtoDataException(
"Virtual host [" + virtualHost.getName()
+ "] contains route without cluster or weighted cluster");
}
}
return Collections.unmodifiableList(routes);
}

Expand Down
42 changes: 35 additions & 7 deletions xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void convertRoute() {
new Route(
new RouteMatch(
null, "/service/method", null, null, Collections.<HeaderMatcher>emptyList()),
new RouteAction("cluster-foo", null, null)));
new RouteAction("cluster-foo", null)));

io.envoyproxy.envoy.api.v2.route.Route unsupportedProto =
io.envoyproxy.envoy.api.v2.route.Route.newBuilder()
Expand All @@ -118,6 +118,39 @@ public void convertRoute() {
assertThat(unsupportedStruct.getStruct()).isNull();
}

@Test
public void convertRoute_skipWithUnsupportedMatcher() {
io.envoyproxy.envoy.api.v2.route.Route proto =
io.envoyproxy.envoy.api.v2.route.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder()
.setPath("/service/method")
.addQueryParameters(
io.envoyproxy.envoy.api.v2.route.QueryParameterMatcher
.getDefaultInstance()))
.setRoute(
io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
}

@Test
public void convertRoute_skipWithUnsupportedAction() {
io.envoyproxy.envoy.api.v2.route.Route proto =
io.envoyproxy.envoy.api.v2.route.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder()
.setClusterHeader("some cluster header"))
.build();
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
}

@Test
public void isDefaultRoute() {
StructOrError<Route> struct1 = Route.fromEnvoyProtoRoute(buildSimpleRouteProto("", null));
Expand Down Expand Up @@ -315,7 +348,6 @@ public void convertRouteAction() {
StructOrError<RouteAction> struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo");
assertThat(struct1.getStruct().getClusterHeader()).isNull();
assertThat(struct1.getStruct().getWeightedCluster()).isNull();

// cluster_specifier = cluster_header
Expand All @@ -324,10 +356,7 @@ public void convertRouteAction() {
.setClusterHeader("cluster-bar")
.build();
StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2);
assertThat(struct2.getErrorDetail()).isNull();
assertThat(struct2.getStruct().getCluster()).isNull();
assertThat(struct2.getStruct().getClusterHeader()).isEqualTo("cluster-bar");
assertThat(struct2.getStruct().getWeightedCluster()).isNull();
assertThat(struct2).isNull();

// cluster_specifier = weighted_cluster
io.envoyproxy.envoy.api.v2.route.RouteAction proto3 =
Expand All @@ -342,7 +371,6 @@ public void convertRouteAction() {
StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3);
assertThat(struct3.getErrorDetail()).isNull();
assertThat(struct3.getStruct().getCluster()).isNull();
assertThat(struct3.getStruct().getClusterHeader()).isNull();
assertThat(struct3.getStruct().getWeightedCluster())
.containsExactly(new ClusterWeight("cluster-baz", 100));

Expand Down
41 changes: 29 additions & 12 deletions xds/src/test/java/io/grpc/xds/XdsClientImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import io.envoyproxy.envoy.api.v2.core.HealthStatus;
import io.envoyproxy.envoy.api.v2.core.Node;
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats;
import io.envoyproxy.envoy.api.v2.route.QueryParameterMatcher;
import io.envoyproxy.envoy.api.v2.route.RedirectAction;
import io.envoyproxy.envoy.api.v2.route.Route;
import io.envoyproxy.envoy.api.v2.route.RouteAction;
Expand Down Expand Up @@ -727,18 +728,14 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() {
new EnvoyProtoData.RouteMatch(
/* prefix= */ null,
/* path= */ "/service1/method1"),
new EnvoyProtoData.RouteAction(
"cl1.googleapis.com",
null,
null)));
new EnvoyProtoData.RouteAction("cl1.googleapis.com", null)));
assertThat(routes.get(1)).isEqualTo(
new EnvoyProtoData.Route(
// path match with weighted cluster route
new EnvoyProtoData.RouteMatch(
/* prefix= */ null,
/* path= */ "/service2/method2"),
new EnvoyProtoData.RouteAction(
null,
null,
ImmutableList.of(
new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30),
Expand All @@ -750,20 +747,15 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() {
new EnvoyProtoData.RouteMatch(
/* prefix= */ "/service1/",
/* path= */ null),
new EnvoyProtoData.RouteAction(
"cl1.googleapis.com",
null,
null)));
new EnvoyProtoData.RouteAction("cl1.googleapis.com", null)));
assertThat(routes.get(3)).isEqualTo(
new EnvoyProtoData.Route(
// default match with cluster route
new EnvoyProtoData.RouteMatch(
/* prefix= */ "",
/* path= */ null),
new EnvoyProtoData.RouteAction(
"cluster.googleapis.com",
null,
null)));
"cluster.googleapis.com", null)));
}

/**
Expand Down Expand Up @@ -3472,6 +3464,31 @@ public void populateRoutesInVirtualHost_lastRouteIsNotDefaultRoute() {
XdsClientImpl.populateRoutesInVirtualHost(virtualHost);
}

@Test
public void populateRoutesInVirtualHost_NoUsableRoute() {
VirtualHost virtualHost =
VirtualHost.newBuilder()
.setName("virtualhost00.googleapis.com") // don't care
.addDomains(TARGET_AUTHORITY)
.addRoutes(
// route with unsupported action
Route.newBuilder()
.setRoute(RouteAction.newBuilder().setClusterHeader("cluster header string"))
.setMatch(RouteMatch.newBuilder().setPrefix("/")))
.addRoutes(
// route with unsupported matcher type
Route.newBuilder()
.setRoute(RouteAction.newBuilder().setCluster("cluster.googleapis.com"))
.setMatch(
RouteMatch.newBuilder()
.setPrefix("/")
.addQueryParameters(QueryParameterMatcher.getDefaultInstance())))
.build();

thrown.expect(XdsClientImpl.InvalidProtoDataException.class);
XdsClientImpl.populateRoutesInVirtualHost(virtualHost);
}

@Test
public void messagePrinter_printLdsResponse() {
MessagePrinter printer = new MessagePrinter();
Expand Down

0 comments on commit a86fc47

Please sign in to comment.