xds: change route data validation logic (#7047)

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.
This commit is contained in:
Chengyuan Zhang 2020-05-18 23:23:25 +00:00 committed by GitHub
parent 02e3c00c39
commit a86fc47c04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 60 deletions

View File

@ -500,6 +500,9 @@ final class EnvoyProtoData {
default: default:
return StructOrError.fromError("Unknown action type: " + proto.getActionCase()); return StructOrError.fromError("Unknown action type: " + proto.getActionCase());
} }
if (routeAction == null) {
return null;
}
if (routeAction.getErrorDetail() != null) { if (routeAction.getErrorDetail() != null) {
return StructOrError.fromError( return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail()); "Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail());
@ -931,16 +934,11 @@ final class EnvoyProtoData {
@Nullable @Nullable
private final String cluster; private final String cluster;
@Nullable @Nullable
private final String clusterHeader;
@Nullable
private final List<ClusterWeight> weightedClusters; private final List<ClusterWeight> weightedClusters;
@VisibleForTesting @VisibleForTesting
RouteAction( RouteAction(@Nullable String cluster, @Nullable List<ClusterWeight> weightedClusters) {
@Nullable String cluster, @Nullable String clusterHeader,
@Nullable List<ClusterWeight> weightedClusters) {
this.cluster = cluster; this.cluster = cluster;
this.clusterHeader = clusterHeader;
this.weightedClusters = weightedClusters; this.weightedClusters = weightedClusters;
} }
@ -949,11 +947,6 @@ final class EnvoyProtoData {
return cluster; return cluster;
} }
@Nullable
String getClusterHeader() {
return clusterHeader;
}
@Nullable @Nullable
List<ClusterWeight> getWeightedCluster() { List<ClusterWeight> getWeightedCluster() {
return weightedClusters; return weightedClusters;
@ -969,13 +962,12 @@ final class EnvoyProtoData {
} }
RouteAction that = (RouteAction) o; RouteAction that = (RouteAction) o;
return Objects.equals(cluster, that.cluster) return Objects.equals(cluster, that.cluster)
&& Objects.equals(clusterHeader, that.clusterHeader) && Objects.equals(weightedClusters, that.weightedClusters);
&& Objects.equals(weightedClusters, that.weightedClusters);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(cluster, clusterHeader, weightedClusters); return Objects.hash(cluster, weightedClusters);
} }
@Override @Override
@ -984,28 +976,24 @@ final class EnvoyProtoData {
if (cluster != null) { if (cluster != null) {
toStringHelper.add("cluster", cluster); toStringHelper.add("cluster", cluster);
} }
if (clusterHeader != null) {
toStringHelper.add("clusterHeader", clusterHeader);
}
if (weightedClusters != null) { if (weightedClusters != null) {
toStringHelper.add("weightedClusters", weightedClusters); toStringHelper.add("weightedClusters", weightedClusters);
} }
return toStringHelper.toString(); return toStringHelper.toString();
} }
@Nullable
@VisibleForTesting @VisibleForTesting
static StructOrError<RouteAction> fromEnvoyProtoRouteAction( static StructOrError<RouteAction> fromEnvoyProtoRouteAction(
io.envoyproxy.envoy.api.v2.route.RouteAction proto) { io.envoyproxy.envoy.api.v2.route.RouteAction proto) {
String cluster = null; String cluster = null;
String clusterHeader = null;
List<ClusterWeight> weightedClusters = null; List<ClusterWeight> weightedClusters = null;
switch (proto.getClusterSpecifierCase()) { switch (proto.getClusterSpecifierCase()) {
case CLUSTER: case CLUSTER:
cluster = proto.getCluster(); cluster = proto.getCluster();
break; break;
case CLUSTER_HEADER: case CLUSTER_HEADER:
clusterHeader = proto.getClusterHeader(); return null;
break;
case WEIGHTED_CLUSTERS: case WEIGHTED_CLUSTERS:
List<io.envoyproxy.envoy.api.v2.route.WeightedCluster.ClusterWeight> clusterWeights List<io.envoyproxy.envoy.api.v2.route.WeightedCluster.ClusterWeight> clusterWeights
= proto.getWeightedClusters().getClustersList(); = proto.getWeightedClusters().getClustersList();
@ -1020,7 +1008,7 @@ 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, clusterHeader, weightedClusters)); return StructOrError.fromStruct(new RouteAction(cluster, weightedClusters));
} }
} }

View File

@ -864,30 +864,10 @@ final class XdsClientImpl extends XdsClient {
"Virtual host [" + virtualHost.getName() "Virtual host [" + virtualHost.getName()
+ "] contains non-default route as the last route"); + "] contains non-default route as the last route");
} }
// We only validate the default route unless path matching is enabled.
if (!enableExperimentalRouting) { if (!enableExperimentalRouting) {
EnvoyProtoData.Route defaultRoute = Iterables.getLast(routes); 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); 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); return Collections.unmodifiableList(routes);
} }

View File

@ -105,7 +105,7 @@ public class EnvoyProtoDataTest {
new Route( new Route(
new RouteMatch( new RouteMatch(
null, "/service/method", null, null, Collections.<HeaderMatcher>emptyList()), 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 unsupportedProto =
io.envoyproxy.envoy.api.v2.route.Route.newBuilder() io.envoyproxy.envoy.api.v2.route.Route.newBuilder()
@ -118,6 +118,39 @@ public class EnvoyProtoDataTest {
assertThat(unsupportedStruct.getStruct()).isNull(); 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 @Test
public void isDefaultRoute() { public void isDefaultRoute() {
StructOrError<Route> struct1 = Route.fromEnvoyProtoRoute(buildSimpleRouteProto("", null)); StructOrError<Route> struct1 = Route.fromEnvoyProtoRoute(buildSimpleRouteProto("", null));
@ -315,7 +348,6 @@ public class EnvoyProtoDataTest {
StructOrError<RouteAction> struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1); StructOrError<RouteAction> struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1);
assertThat(struct1.getErrorDetail()).isNull(); assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo"); assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo");
assertThat(struct1.getStruct().getClusterHeader()).isNull();
assertThat(struct1.getStruct().getWeightedCluster()).isNull(); assertThat(struct1.getStruct().getWeightedCluster()).isNull();
// cluster_specifier = cluster_header // cluster_specifier = cluster_header
@ -324,10 +356,7 @@ public class EnvoyProtoDataTest {
.setClusterHeader("cluster-bar") .setClusterHeader("cluster-bar")
.build(); .build();
StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2); StructOrError<RouteAction> struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2);
assertThat(struct2.getErrorDetail()).isNull(); assertThat(struct2).isNull();
assertThat(struct2.getStruct().getCluster()).isNull();
assertThat(struct2.getStruct().getClusterHeader()).isEqualTo("cluster-bar");
assertThat(struct2.getStruct().getWeightedCluster()).isNull();
// cluster_specifier = weighted_cluster // cluster_specifier = weighted_cluster
io.envoyproxy.envoy.api.v2.route.RouteAction proto3 = io.envoyproxy.envoy.api.v2.route.RouteAction proto3 =
@ -342,7 +371,6 @@ public class EnvoyProtoDataTest {
StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3); StructOrError<RouteAction> struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3);
assertThat(struct3.getErrorDetail()).isNull(); assertThat(struct3.getErrorDetail()).isNull();
assertThat(struct3.getStruct().getCluster()).isNull(); assertThat(struct3.getStruct().getCluster()).isNull();
assertThat(struct3.getStruct().getClusterHeader()).isNull();
assertThat(struct3.getStruct().getWeightedCluster()) assertThat(struct3.getStruct().getWeightedCluster())
.containsExactly(new ClusterWeight("cluster-baz", 100)); .containsExactly(new ClusterWeight("cluster-baz", 100));

View File

@ -61,6 +61,7 @@ import io.envoyproxy.envoy.api.v2.core.ConfigSource;
import io.envoyproxy.envoy.api.v2.core.HealthStatus; import io.envoyproxy.envoy.api.v2.core.HealthStatus;
import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.core.Node;
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; 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.RedirectAction;
import io.envoyproxy.envoy.api.v2.route.Route; import io.envoyproxy.envoy.api.v2.route.Route;
import io.envoyproxy.envoy.api.v2.route.RouteAction; import io.envoyproxy.envoy.api.v2.route.RouteAction;
@ -727,10 +728,7 @@ public class XdsClientImplTest {
new EnvoyProtoData.RouteMatch( new EnvoyProtoData.RouteMatch(
/* prefix= */ null, /* prefix= */ null,
/* path= */ "/service1/method1"), /* path= */ "/service1/method1"),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction("cl1.googleapis.com", null)));
"cl1.googleapis.com",
null,
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
@ -738,7 +736,6 @@ public class XdsClientImplTest {
/* prefix= */ null, /* prefix= */ null,
/* path= */ "/service2/method2"), /* path= */ "/service2/method2"),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction(
null,
null, null,
ImmutableList.of( ImmutableList.of(
new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30),
@ -750,10 +747,7 @@ public class XdsClientImplTest {
new EnvoyProtoData.RouteMatch( new EnvoyProtoData.RouteMatch(
/* prefix= */ "/service1/", /* prefix= */ "/service1/",
/* path= */ null), /* path= */ null),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction("cl1.googleapis.com", null)));
"cl1.googleapis.com",
null,
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
@ -761,9 +755,7 @@ public class XdsClientImplTest {
/* prefix= */ "", /* prefix= */ "",
/* path= */ null), /* path= */ null),
new EnvoyProtoData.RouteAction( new EnvoyProtoData.RouteAction(
"cluster.googleapis.com", "cluster.googleapis.com", null)));
null,
null)));
} }
/** /**
@ -3472,6 +3464,31 @@ public class XdsClientImplTest {
XdsClientImpl.populateRoutesInVirtualHost(virtualHost); 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 @Test
public void messagePrinter_printLdsResponse() { public void messagePrinter_printLdsResponse() {
MessagePrinter printer = new MessagePrinter(); MessagePrinter printer = new MessagePrinter();