xref: /aosp_15_r20/external/grpc-grpc-java/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
1 /*
2  * Copyright 2021 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.xds;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static io.envoyproxy.envoy.config.route.v3.RouteAction.ClusterSpecifierCase.CLUSTER_SPECIFIER_PLUGIN;
21 import static org.junit.Assert.fail;
22 
23 import com.github.udpa.udpa.type.v1.TypedStruct;
24 import com.google.common.collect.ImmutableMap;
25 import com.google.common.collect.ImmutableSet;
26 import com.google.common.collect.Iterables;
27 import com.google.protobuf.Any;
28 import com.google.protobuf.BoolValue;
29 import com.google.protobuf.ByteString;
30 import com.google.protobuf.Duration;
31 import com.google.protobuf.FloatValue;
32 import com.google.protobuf.InvalidProtocolBufferException;
33 import com.google.protobuf.Message;
34 import com.google.protobuf.StringValue;
35 import com.google.protobuf.Struct;
36 import com.google.protobuf.UInt32Value;
37 import com.google.protobuf.Value;
38 import com.google.protobuf.util.Durations;
39 import com.google.re2j.Pattern;
40 import io.envoyproxy.envoy.config.cluster.v3.Cluster;
41 import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType;
42 import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig;
43 import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy;
44 import io.envoyproxy.envoy.config.cluster.v3.LoadBalancingPolicy;
45 import io.envoyproxy.envoy.config.core.v3.Address;
46 import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource;
47 import io.envoyproxy.envoy.config.core.v3.CidrRange;
48 import io.envoyproxy.envoy.config.core.v3.ConfigSource;
49 import io.envoyproxy.envoy.config.core.v3.DataSource;
50 import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
51 import io.envoyproxy.envoy.config.core.v3.Locality;
52 import io.envoyproxy.envoy.config.core.v3.PathConfigSource;
53 import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
54 import io.envoyproxy.envoy.config.core.v3.SelfConfigSource;
55 import io.envoyproxy.envoy.config.core.v3.SocketAddress;
56 import io.envoyproxy.envoy.config.core.v3.TrafficDirection;
57 import io.envoyproxy.envoy.config.core.v3.TransportSocket;
58 import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig;
59 import io.envoyproxy.envoy.config.endpoint.v3.Endpoint;
60 import io.envoyproxy.envoy.config.listener.v3.Filter;
61 import io.envoyproxy.envoy.config.listener.v3.FilterChain;
62 import io.envoyproxy.envoy.config.listener.v3.FilterChainMatch;
63 import io.envoyproxy.envoy.config.listener.v3.Listener;
64 import io.envoyproxy.envoy.config.listener.v3.ListenerFilter;
65 import io.envoyproxy.envoy.config.rbac.v3.Permission;
66 import io.envoyproxy.envoy.config.rbac.v3.Policy;
67 import io.envoyproxy.envoy.config.rbac.v3.Principal;
68 import io.envoyproxy.envoy.config.rbac.v3.RBAC;
69 import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action;
70 import io.envoyproxy.envoy.config.route.v3.DirectResponseAction;
71 import io.envoyproxy.envoy.config.route.v3.FilterAction;
72 import io.envoyproxy.envoy.config.route.v3.NonForwardingAction;
73 import io.envoyproxy.envoy.config.route.v3.RedirectAction;
74 import io.envoyproxy.envoy.config.route.v3.RetryPolicy;
75 import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff;
76 import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.ConnectionProperties;
77 import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.FilterState;
78 import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header;
79 import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.QueryParameter;
80 import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
81 import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
82 import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
83 import io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay;
84 import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort;
85 import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
86 import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
87 import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router;
88 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
89 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
90 import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
91 import io.envoyproxy.envoy.extensions.load_balancing_policies.client_side_weighted_round_robin.v3.ClientSideWeightedRoundRobin;
92 import io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality;
93 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
94 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
95 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
96 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
97 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
98 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext;
99 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
100 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
101 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters;
102 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext;
103 import io.envoyproxy.envoy.type.matcher.v3.RegexMatchAndSubstitute;
104 import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
105 import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher.GoogleRE2;
106 import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
107 import io.envoyproxy.envoy.type.v3.FractionalPercent;
108 import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
109 import io.envoyproxy.envoy.type.v3.Int64Range;
110 import io.grpc.ClientInterceptor;
111 import io.grpc.InsecureChannelCredentials;
112 import io.grpc.LoadBalancer;
113 import io.grpc.LoadBalancerRegistry;
114 import io.grpc.Status.Code;
115 import io.grpc.internal.JsonUtil;
116 import io.grpc.internal.ServiceConfigUtil;
117 import io.grpc.internal.ServiceConfigUtil.LbConfig;
118 import io.grpc.lookup.v1.GrpcKeyBuilder;
119 import io.grpc.lookup.v1.GrpcKeyBuilder.Name;
120 import io.grpc.lookup.v1.NameMatcher;
121 import io.grpc.lookup.v1.RouteLookupClusterSpecifier;
122 import io.grpc.lookup.v1.RouteLookupConfig;
123 import io.grpc.xds.Bootstrapper.ServerInfo;
124 import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig;
125 import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig;
126 import io.grpc.xds.Endpoints.LbEndpoint;
127 import io.grpc.xds.Endpoints.LocalityLbEndpoints;
128 import io.grpc.xds.Filter.FilterConfig;
129 import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig;
130 import io.grpc.xds.VirtualHost.Route;
131 import io.grpc.xds.VirtualHost.Route.RouteAction;
132 import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
133 import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
134 import io.grpc.xds.VirtualHost.Route.RouteMatch;
135 import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
136 import io.grpc.xds.WeightedRoundRobinLoadBalancer.WeightedRoundRobinLoadBalancerConfig;
137 import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
138 import io.grpc.xds.XdsClusterResource.CdsUpdate;
139 import io.grpc.xds.XdsResourceType.StructOrError;
140 import io.grpc.xds.internal.Matchers;
141 import io.grpc.xds.internal.Matchers.FractionMatcher;
142 import io.grpc.xds.internal.Matchers.HeaderMatcher;
143 import java.util.Arrays;
144 import java.util.Collections;
145 import java.util.List;
146 import java.util.Map;
147 import java.util.concurrent.ScheduledExecutorService;
148 import java.util.concurrent.TimeUnit;
149 import javax.annotation.Nullable;
150 import org.junit.After;
151 import org.junit.Before;
152 import org.junit.Rule;
153 import org.junit.Test;
154 import org.junit.rules.ExpectedException;
155 import org.junit.runner.RunWith;
156 import org.junit.runners.JUnit4;
157 
158 @RunWith(JUnit4.class)
159 public class XdsClientImplDataTest {
160 
161   private static final ServerInfo LRS_SERVER_INFO =
162       ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create());
163 
164   @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
165   @Rule
166   public final ExpectedException thrown = ExpectedException.none();
167   private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
168   private boolean originalEnableRouteLookup;
169   private boolean originalEnableLeastRequest;
170   private boolean originalEnableWrr;
171 
172   @Before
setUp()173   public void setUp() {
174     originalEnableRouteLookup = XdsResourceType.enableRouteLookup;
175     originalEnableLeastRequest = XdsResourceType.enableLeastRequest;
176     assertThat(originalEnableLeastRequest).isFalse();
177     originalEnableWrr = XdsResourceType.enableWrr;
178     assertThat(originalEnableWrr).isTrue();
179   }
180 
181   @After
tearDown()182   public void tearDown() {
183     XdsResourceType.enableRouteLookup = originalEnableRouteLookup;
184     XdsResourceType.enableLeastRequest = originalEnableLeastRequest;
185     XdsResourceType.enableWrr = originalEnableWrr;
186   }
187 
188   @Test
parseRoute_withRouteAction()189   public void parseRoute_withRouteAction() {
190     io.envoyproxy.envoy.config.route.v3.Route proto =
191         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
192             .setName("route-blade")
193             .setMatch(
194                 io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
195                     .setPath("/service/method"))
196             .setRoute(
197                 io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
198                     .setCluster("cluster-foo"))
199             .build();
200     StructOrError<Route> struct = XdsRouteConfigureResource.parseRoute(
201         proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of());
202     assertThat(struct.getErrorDetail()).isNull();
203     assertThat(struct.getStruct())
204         .isEqualTo(
205             Route.forAction(
206                 RouteMatch.create(PathMatcher.fromPath("/service/method", false),
207                     Collections.<HeaderMatcher>emptyList(), null),
208                 RouteAction.forCluster(
209                     "cluster-foo", Collections.<HashPolicy>emptyList(), null, null),
210                 ImmutableMap.<String, FilterConfig>of()));
211   }
212 
213   @Test
parseRoute_withNonForwardingAction()214   public void parseRoute_withNonForwardingAction() {
215     io.envoyproxy.envoy.config.route.v3.Route proto =
216         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
217             .setName("route-blade")
218             .setMatch(
219                 io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
220                     .setPath("/service/method"))
221             .setNonForwardingAction(NonForwardingAction.getDefaultInstance())
222             .build();
223     StructOrError<Route> struct = XdsRouteConfigureResource.parseRoute(
224         proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of());
225     assertThat(struct.getStruct())
226         .isEqualTo(
227             Route.forNonForwardingAction(
228                 RouteMatch.create(PathMatcher.fromPath("/service/method", false),
229                     Collections.<HeaderMatcher>emptyList(), null),
230                 ImmutableMap.<String, FilterConfig>of()));
231   }
232 
233   @Test
parseRoute_withUnsupportedActionTypes()234   public void parseRoute_withUnsupportedActionTypes() {
235     StructOrError<Route> res;
236     io.envoyproxy.envoy.config.route.v3.Route redirectRoute =
237         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
238             .setName("route-blade")
239             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
240             .setRedirect(RedirectAction.getDefaultInstance())
241             .build();
242     res = XdsRouteConfigureResource.parseRoute(
243         redirectRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of());
244     assertThat(res.getStruct()).isNull();
245     assertThat(res.getErrorDetail())
246         .isEqualTo("Route [route-blade] with unknown action type: REDIRECT");
247 
248     io.envoyproxy.envoy.config.route.v3.Route directResponseRoute =
249         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
250             .setName("route-blade")
251             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
252             .setDirectResponse(DirectResponseAction.getDefaultInstance())
253             .build();
254     res = XdsRouteConfigureResource.parseRoute(
255         directResponseRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of());
256     assertThat(res.getStruct()).isNull();
257     assertThat(res.getErrorDetail())
258         .isEqualTo("Route [route-blade] with unknown action type: DIRECT_RESPONSE");
259 
260     io.envoyproxy.envoy.config.route.v3.Route filterRoute =
261         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
262             .setName("route-blade")
263             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
264             .setFilterAction(FilterAction.getDefaultInstance())
265             .build();
266     res = XdsRouteConfigureResource.parseRoute(
267         filterRoute, filterRegistry, ImmutableMap.of(), ImmutableSet.of());
268     assertThat(res.getStruct()).isNull();
269     assertThat(res.getErrorDetail())
270         .isEqualTo("Route [route-blade] with unknown action type: FILTER_ACTION");
271   }
272 
273   @Test
parseRoute_skipRouteWithUnsupportedMatcher()274   public void parseRoute_skipRouteWithUnsupportedMatcher() {
275     io.envoyproxy.envoy.config.route.v3.Route proto =
276         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
277             .setName("ignore me")
278             .setMatch(
279                 io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
280                     .setPath("/service/method")
281                     .addQueryParameters(
282                         io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher
283                             .getDefaultInstance()))  // query parameter not supported
284             .setRoute(
285                 io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
286                     .setCluster("cluster-foo"))
287             .build();
288     assertThat(XdsRouteConfigureResource.parseRoute(
289             proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of()))
290         .isNull();
291   }
292 
293   @Test
parseRoute_skipRouteWithUnsupportedAction()294   public void parseRoute_skipRouteWithUnsupportedAction() {
295     io.envoyproxy.envoy.config.route.v3.Route proto =
296         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
297             .setName("ignore me")
298             .setMatch(
299                 io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
300                     .setPath("/service/method"))
301             .setRoute(
302                 io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
303                     .setClusterHeader("cluster header"))  // cluster_header action not supported
304             .build();
305     assertThat(XdsRouteConfigureResource.parseRoute(
306             proto, filterRegistry, ImmutableMap.of(), ImmutableSet.of()))
307         .isNull();
308   }
309 
310   @Test
311   @SuppressWarnings("deprecation")
parseRouteMatch_withHeaderMatcher()312   public void parseRouteMatch_withHeaderMatcher() {
313     io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
314         io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
315             .setPrefix("")
316             .addHeaders(
317                 io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
318                     .setName(":scheme")
319                     .setPrefixMatch("http"))
320             .addHeaders(
321                 io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
322                     .setName(":method")
323                     .setExactMatch("PUT"))
324             .build();
325     StructOrError<RouteMatch> struct = XdsRouteConfigureResource.parseRouteMatch(proto);
326     assertThat(struct.getErrorDetail()).isNull();
327     assertThat(struct.getStruct())
328         .isEqualTo(
329             RouteMatch.create(
330                 PathMatcher.fromPrefix("", false),
331                 Arrays.asList(
332                     HeaderMatcher.forPrefix(":scheme", "http", false),
333                     HeaderMatcher.forExactValue(":method", "PUT", false)),
334                 null));
335   }
336 
337   @Test
parseRouteMatch_withRuntimeFractionMatcher()338   public void parseRouteMatch_withRuntimeFractionMatcher() {
339     io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
340         io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
341             .setPrefix("")
342             .setRuntimeFraction(
343                 RuntimeFractionalPercent.newBuilder()
344                     .setDefaultValue(
345                         FractionalPercent.newBuilder()
346                             .setNumerator(30)
347                             .setDenominator(FractionalPercent.DenominatorType.HUNDRED)))
348             .build();
349     StructOrError<RouteMatch> struct = XdsRouteConfigureResource.parseRouteMatch(proto);
350     assertThat(struct.getErrorDetail()).isNull();
351     assertThat(struct.getStruct())
352         .isEqualTo(
353             RouteMatch.create(
354                 PathMatcher.fromPrefix( "", false), Collections.<HeaderMatcher>emptyList(),
355                 FractionMatcher.create(30, 100)));
356   }
357 
358   @Test
parsePathMatcher_withFullPath()359   public void parsePathMatcher_withFullPath() {
360     io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
361         io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
362             .setPath("/service/method")
363             .build();
364     StructOrError<PathMatcher> struct = XdsRouteConfigureResource.parsePathMatcher(proto);
365     assertThat(struct.getErrorDetail()).isNull();
366     assertThat(struct.getStruct()).isEqualTo(
367         PathMatcher.fromPath("/service/method", false));
368   }
369 
370   @Test
parsePathMatcher_withPrefix()371   public void parsePathMatcher_withPrefix() {
372     io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
373         io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build();
374     StructOrError<PathMatcher> struct = XdsRouteConfigureResource.parsePathMatcher(proto);
375     assertThat(struct.getErrorDetail()).isNull();
376     assertThat(struct.getStruct()).isEqualTo(
377         PathMatcher.fromPrefix("/", false));
378   }
379 
380   @Test
parsePathMatcher_withSafeRegEx()381   public void parsePathMatcher_withSafeRegEx() {
382     io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
383         io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
384             .setSafeRegex(RegexMatcher.newBuilder().setRegex("."))
385             .build();
386     StructOrError<PathMatcher> struct = XdsRouteConfigureResource.parsePathMatcher(proto);
387     assertThat(struct.getErrorDetail()).isNull();
388     assertThat(struct.getStruct()).isEqualTo(PathMatcher.fromRegEx(Pattern.compile(".")));
389   }
390 
391   @Test
392   @SuppressWarnings("deprecation")
parseHeaderMatcher_withExactMatch()393   public void parseHeaderMatcher_withExactMatch() {
394     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
395         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
396             .setName(":method")
397             .setExactMatch("PUT")
398             .build();
399     StructOrError<HeaderMatcher> struct1 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
400     assertThat(struct1.getErrorDetail()).isNull();
401     assertThat(struct1.getStruct()).isEqualTo(
402         HeaderMatcher.forExactValue(":method", "PUT", false));
403   }
404 
405   @Test
406   @SuppressWarnings("deprecation")
parseHeaderMatcher_withSafeRegExMatch()407   public void parseHeaderMatcher_withSafeRegExMatch() {
408     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
409         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
410             .setName(":method")
411             .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*"))
412             .build();
413     StructOrError<HeaderMatcher> struct3 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
414     assertThat(struct3.getErrorDetail()).isNull();
415     assertThat(struct3.getStruct()).isEqualTo(
416         HeaderMatcher.forSafeRegEx(":method", Pattern.compile("P*"), false));
417   }
418 
419   @Test
parseHeaderMatcher_withRangeMatch()420   public void parseHeaderMatcher_withRangeMatch() {
421     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
422         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
423             .setName("timeout")
424             .setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L))
425             .build();
426     StructOrError<HeaderMatcher> struct4 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
427     assertThat(struct4.getErrorDetail()).isNull();
428     assertThat(struct4.getStruct()).isEqualTo(
429         HeaderMatcher.forRange("timeout", HeaderMatcher.Range.create(10L, 20L), false));
430   }
431 
432   @Test
parseHeaderMatcher_withPresentMatch()433   public void parseHeaderMatcher_withPresentMatch() {
434     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
435         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
436             .setName("user-agent")
437             .setPresentMatch(true)
438             .build();
439     StructOrError<HeaderMatcher> struct5 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
440     assertThat(struct5.getErrorDetail()).isNull();
441     assertThat(struct5.getStruct()).isEqualTo(
442         HeaderMatcher.forPresent("user-agent", true, false));
443   }
444 
445   @Test
446   @SuppressWarnings("deprecation")
parseHeaderMatcher_withPrefixMatch()447   public void parseHeaderMatcher_withPrefixMatch() {
448     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
449         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
450             .setName("authority")
451             .setPrefixMatch("service-foo")
452             .build();
453     StructOrError<HeaderMatcher> struct6 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
454     assertThat(struct6.getErrorDetail()).isNull();
455     assertThat(struct6.getStruct()).isEqualTo(
456         HeaderMatcher.forPrefix("authority", "service-foo", false));
457   }
458 
459   @Test
460   @SuppressWarnings("deprecation")
parseHeaderMatcher_withSuffixMatch()461   public void parseHeaderMatcher_withSuffixMatch() {
462     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
463         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
464             .setName("authority")
465             .setSuffixMatch("googleapis.com")
466             .build();
467     StructOrError<HeaderMatcher> struct7 = XdsRouteConfigureResource.parseHeaderMatcher(proto);
468     assertThat(struct7.getErrorDetail()).isNull();
469     assertThat(struct7.getStruct()).isEqualTo(
470         HeaderMatcher.forSuffix("authority", "googleapis.com", false));
471   }
472 
473   @Test
474   @SuppressWarnings("deprecation")
parseHeaderMatcher_malformedRegExPattern()475   public void parseHeaderMatcher_malformedRegExPattern() {
476     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
477         io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
478             .setName(":method")
479             .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("["))
480             .build();
481     StructOrError<HeaderMatcher> struct = XdsRouteConfigureResource.parseHeaderMatcher(proto);
482     assertThat(struct.getErrorDetail()).isNotNull();
483     assertThat(struct.getStruct()).isNull();
484   }
485 
486   @Test
487   @SuppressWarnings("deprecation")
parseHeaderMatcher_withStringMatcher()488   public void parseHeaderMatcher_withStringMatcher() {
489     io.envoyproxy.envoy.type.matcher.v3.StringMatcher stringMatcherProto =
490             io.envoyproxy.envoy.type.matcher.v3.StringMatcher.newBuilder()
491                     .setPrefix("service-foo")
492                     .setIgnoreCase(false)
493                     .build();
494 
495     io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
496             io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
497                     .setName("authority")
498                     .setStringMatch(stringMatcherProto)
499                     .setInvertMatch(false)
500                     .build();
501     StructOrError<HeaderMatcher> struct = XdsRouteConfigureResource.parseHeaderMatcher(proto);
502     assertThat(struct.getErrorDetail()).isNull();
503     assertThat(struct.getStruct()).isEqualTo(
504             HeaderMatcher.forString("authority", Matchers.StringMatcher
505                     .forPrefix("service-foo", false), false));
506   }
507 
508   @Test
parseRouteAction_withCluster()509   public void parseRouteAction_withCluster() {
510     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
511         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
512             .setCluster("cluster-foo")
513             .build();
514     StructOrError<RouteAction> struct =
515         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
516           ImmutableMap.of(), ImmutableSet.of());
517     assertThat(struct.getErrorDetail()).isNull();
518     assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo");
519     assertThat(struct.getStruct().weightedClusters()).isNull();
520   }
521 
522   @Test
parseRouteAction_withWeightedCluster()523   public void parseRouteAction_withWeightedCluster() {
524     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
525         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
526             .setWeightedClusters(
527                 WeightedCluster.newBuilder()
528                     .addClusters(
529                         WeightedCluster.ClusterWeight
530                             .newBuilder()
531                             .setName("cluster-foo")
532                             .setWeight(UInt32Value.newBuilder().setValue(30)))
533                     .addClusters(WeightedCluster.ClusterWeight
534                         .newBuilder()
535                         .setName("cluster-bar")
536                         .setWeight(UInt32Value.newBuilder().setValue(70))))
537             .build();
538     StructOrError<RouteAction> struct =
539         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
540           ImmutableMap.of(), ImmutableSet.of());
541     assertThat(struct.getErrorDetail()).isNull();
542     assertThat(struct.getStruct().cluster()).isNull();
543     assertThat(struct.getStruct().weightedClusters()).containsExactly(
544         ClusterWeight.create("cluster-foo", 30, ImmutableMap.<String, FilterConfig>of()),
545         ClusterWeight.create("cluster-bar", 70, ImmutableMap.<String, FilterConfig>of()));
546   }
547 
548   @Test
parseRouteAction_weightedClusterSum()549   public void parseRouteAction_weightedClusterSum() {
550     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
551         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
552             .setWeightedClusters(
553                 WeightedCluster.newBuilder()
554                     .addClusters(
555                         WeightedCluster.ClusterWeight
556                             .newBuilder()
557                             .setName("cluster-foo")
558                             .setWeight(UInt32Value.newBuilder().setValue(0)))
559                     .addClusters(WeightedCluster.ClusterWeight
560                         .newBuilder()
561                         .setName("cluster-bar")
562                         .setWeight(UInt32Value.newBuilder().setValue(0))))
563             .build();
564     StructOrError<RouteAction> struct =
565         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
566             ImmutableMap.of(), ImmutableSet.of());
567     assertThat(struct.getErrorDetail()).isEqualTo("Sum of cluster weights should be above 0.");
568   }
569 
570   @Test
parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax()571   public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() {
572     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
573         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
574             .setCluster("cluster-foo")
575             .setMaxStreamDuration(
576                 MaxStreamDuration.newBuilder()
577                     .setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
578                     .setMaxStreamDuration(Durations.fromMillis(20L)))
579             .build();
580     StructOrError<RouteAction> struct =
581         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
582             ImmutableMap.of(), ImmutableSet.of());
583     assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
584   }
585 
586   @Test
parseRouteAction_withTimeoutByMaxStreamDuration()587   public void parseRouteAction_withTimeoutByMaxStreamDuration() {
588     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
589         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
590             .setCluster("cluster-foo")
591             .setMaxStreamDuration(
592                 MaxStreamDuration.newBuilder()
593                     .setMaxStreamDuration(Durations.fromSeconds(5L)))
594             .build();
595     StructOrError<RouteAction> struct =
596         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
597            ImmutableMap.of(), ImmutableSet.of());
598     assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
599   }
600 
601   @Test
parseRouteAction_withTimeoutUnset()602   public void parseRouteAction_withTimeoutUnset() {
603     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
604         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
605             .setCluster("cluster-foo")
606             .build();
607     StructOrError<RouteAction> struct =
608         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
609           ImmutableMap.of(), ImmutableSet.of());
610     assertThat(struct.getStruct().timeoutNano()).isNull();
611   }
612 
613   @Test
parseRouteAction_withRetryPolicy()614   public void parseRouteAction_withRetryPolicy() {
615     RetryPolicy.Builder builder = RetryPolicy.newBuilder()
616         .setNumRetries(UInt32Value.of(3))
617         .setRetryBackOff(
618             RetryBackOff.newBuilder()
619                 .setBaseInterval(Durations.fromMillis(500))
620                 .setMaxInterval(Durations.fromMillis(600)))
621         .setPerTryTimeout(Durations.fromMillis(300))
622         .setRetryOn(
623             "cancelled,deadline-exceeded,internal,resource-exhausted,unavailable");
624     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
625         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
626             .setCluster("cluster-foo")
627             .setRetryPolicy(builder.build())
628             .build();
629     StructOrError<RouteAction> struct =
630         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
631           ImmutableMap.of(), ImmutableSet.of());
632     RouteAction.RetryPolicy retryPolicy = struct.getStruct().retryPolicy();
633     assertThat(retryPolicy.maxAttempts()).isEqualTo(4);
634     assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(500));
635     assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(600));
636     // Not supporting per_try_timeout yet.
637     assertThat(retryPolicy.perAttemptRecvTimeout()).isEqualTo(null);
638     assertThat(retryPolicy.retryableStatusCodes()).containsExactly(
639         Code.CANCELLED, Code.DEADLINE_EXCEEDED, Code.INTERNAL, Code.RESOURCE_EXHAUSTED,
640         Code.UNAVAILABLE);
641 
642     // empty retry_on
643     builder = RetryPolicy.newBuilder()
644         .setNumRetries(UInt32Value.of(3))
645         .setRetryBackOff(
646             RetryBackOff.newBuilder()
647                 .setBaseInterval(Durations.fromMillis(500))
648                 .setMaxInterval(Durations.fromMillis(600)))
649         .setPerTryTimeout(Durations.fromMillis(300)); // Not supporting per_try_timeout yet.
650     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
651         .setCluster("cluster-foo")
652         .setRetryPolicy(builder.build())
653         .build();
654     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
655         ImmutableMap.of(), ImmutableSet.of());
656     assertThat(struct.getStruct().retryPolicy()).isNotNull();
657     assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()).isEmpty();
658 
659     // base_interval unset
660     builder
661         .setRetryOn("cancelled")
662         .setRetryBackOff(RetryBackOff.newBuilder().setMaxInterval(Durations.fromMillis(600)));
663     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
664         .setCluster("cluster-foo")
665         .setRetryPolicy(builder)
666         .build();
667     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
668         ImmutableMap.of(), ImmutableSet.of());
669     assertThat(struct.getErrorDetail()).isEqualTo("No base_interval specified in retry_backoff");
670 
671     // max_interval unset
672     builder.setRetryBackOff(RetryBackOff.newBuilder().setBaseInterval(Durations.fromMillis(500)));
673     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
674         .setCluster("cluster-foo")
675         .setRetryPolicy(builder)
676         .build();
677     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
678         ImmutableMap.of(), ImmutableSet.of());
679     retryPolicy = struct.getStruct().retryPolicy();
680     assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(500 * 10));
681 
682     // base_interval < 0
683     builder.setRetryBackOff(RetryBackOff.newBuilder().setBaseInterval(Durations.fromMillis(-1)));
684     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
685         .setCluster("cluster-foo")
686         .setRetryPolicy(builder)
687         .build();
688     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
689         ImmutableMap.of(), ImmutableSet.of());
690     assertThat(struct.getErrorDetail())
691         .isEqualTo("base_interval in retry_backoff must be positive");
692 
693     // base_interval > max_interval > 1ms
694     builder.setRetryBackOff(
695         RetryBackOff.newBuilder()
696             .setBaseInterval(Durations.fromMillis(200)).setMaxInterval(Durations.fromMillis(100)));
697     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
698         .setCluster("cluster-foo")
699         .setRetryPolicy(builder)
700         .build();
701     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
702         ImmutableMap.of(), ImmutableSet.of());
703     assertThat(struct.getErrorDetail())
704         .isEqualTo("max_interval in retry_backoff cannot be less than base_interval");
705 
706     // 1ms > base_interval > max_interval
707     builder.setRetryBackOff(
708         RetryBackOff.newBuilder()
709             .setBaseInterval(Durations.fromNanos(200)).setMaxInterval(Durations.fromNanos(100)));
710     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
711         .setCluster("cluster-foo")
712         .setRetryPolicy(builder)
713         .build();
714     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
715         ImmutableMap.of(), ImmutableSet.of());
716     assertThat(struct.getErrorDetail())
717         .isEqualTo("max_interval in retry_backoff cannot be less than base_interval");
718 
719     // 1ms > max_interval > base_interval
720     builder.setRetryBackOff(
721         RetryBackOff.newBuilder()
722             .setBaseInterval(Durations.fromNanos(100)).setMaxInterval(Durations.fromNanos(200)));
723     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
724         .setCluster("cluster-foo")
725         .setRetryPolicy(builder)
726         .build();
727     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
728         ImmutableMap.of(), ImmutableSet.of());
729     assertThat(struct.getStruct().retryPolicy().initialBackoff())
730         .isEqualTo(Durations.fromMillis(1));
731     assertThat(struct.getStruct().retryPolicy().maxBackoff())
732         .isEqualTo(Durations.fromMillis(1));
733 
734     // retry_backoff unset
735     builder = RetryPolicy.newBuilder()
736         .setNumRetries(UInt32Value.of(3))
737         .setPerTryTimeout(Durations.fromMillis(300))
738         .setRetryOn("cancelled");
739     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
740         .setCluster("cluster-foo")
741         .setRetryPolicy(builder)
742         .build();
743     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
744         ImmutableMap.of(), ImmutableSet.of());
745     retryPolicy = struct.getStruct().retryPolicy();
746     assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(25));
747     assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(250));
748 
749     // unsupported retry_on value
750     builder = RetryPolicy.newBuilder()
751         .setNumRetries(UInt32Value.of(3))
752         .setRetryBackOff(
753             RetryBackOff.newBuilder()
754                 .setBaseInterval(Durations.fromMillis(500))
755                 .setMaxInterval(Durations.fromMillis(600)))
756         .setPerTryTimeout(Durations.fromMillis(300))
757         .setRetryOn("cancelled,unsupported-foo");
758     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
759         .setCluster("cluster-foo")
760         .setRetryPolicy(builder)
761         .build();
762     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
763         ImmutableMap.of(), ImmutableSet.of());
764     assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
765         .containsExactly(Code.CANCELLED);
766 
767     // unsupported retry_on code
768     builder = RetryPolicy.newBuilder()
769         .setNumRetries(UInt32Value.of(3))
770         .setRetryBackOff(
771             RetryBackOff.newBuilder()
772                 .setBaseInterval(Durations.fromMillis(500))
773                 .setMaxInterval(Durations.fromMillis(600)))
774         .setPerTryTimeout(Durations.fromMillis(300))
775         .setRetryOn("cancelled,abort");
776     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
777         .setCluster("cluster-foo")
778         .setRetryPolicy(builder)
779         .build();
780     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
781         ImmutableMap.of(), ImmutableSet.of());
782     assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
783         .containsExactly(Code.CANCELLED);
784 
785     // whitespace in retry_on
786     builder = RetryPolicy.newBuilder()
787         .setNumRetries(UInt32Value.of(3))
788         .setRetryBackOff(
789             RetryBackOff.newBuilder()
790                 .setBaseInterval(Durations.fromMillis(500))
791                 .setMaxInterval(Durations.fromMillis(600)))
792         .setPerTryTimeout(Durations.fromMillis(300))
793         .setRetryOn("abort, , cancelled , ");
794     proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
795         .setCluster("cluster-foo")
796         .setRetryPolicy(builder)
797         .build();
798     struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
799         ImmutableMap.of(), ImmutableSet.of());
800     assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
801         .containsExactly(Code.CANCELLED);
802   }
803 
804   @Test
805   @SuppressWarnings("deprecation")
parseRouteAction_withHashPolicies()806   public void parseRouteAction_withHashPolicies() {
807     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
808         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
809             .setCluster("cluster-foo")
810             .addHashPolicy(
811                 io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
812                     .setHeader(
813                         Header.newBuilder()
814                             .setHeaderName("user-agent")
815                             .setRegexRewrite(
816                                 RegexMatchAndSubstitute.newBuilder()
817                                     .setPattern(
818                                         RegexMatcher.newBuilder()
819                                             .setGoogleRe2(GoogleRE2.getDefaultInstance())
820                                             .setRegex("grpc.*"))
821                                     .setSubstitution("gRPC"))))
822             .addHashPolicy(
823                 io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
824                     .setConnectionProperties(ConnectionProperties.newBuilder().setSourceIp(true))
825                     .setTerminal(true))  // unsupported
826             .addHashPolicy(
827                 io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
828                     .setFilterState(
829                         FilterState.newBuilder()
830                             .setKey(XdsResourceType.HASH_POLICY_FILTER_STATE_KEY)))
831             .addHashPolicy(
832                 io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
833                     .setQueryParameter(
834                         QueryParameter.newBuilder().setName("param"))) // unsupported
835             .build();
836     StructOrError<RouteAction> struct =
837         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
838             ImmutableMap.of(), ImmutableSet.of());
839     List<HashPolicy> policies = struct.getStruct().hashPolicies();
840     assertThat(policies).hasSize(2);
841     assertThat(policies.get(0).type()).isEqualTo(HashPolicy.Type.HEADER);
842     assertThat(policies.get(0).headerName()).isEqualTo("user-agent");
843     assertThat(policies.get(0).isTerminal()).isFalse();
844     assertThat(policies.get(0).regEx().pattern()).isEqualTo("grpc.*");
845     assertThat(policies.get(0).regExSubstitution()).isEqualTo("gRPC");
846 
847     assertThat(policies.get(1).type()).isEqualTo(HashPolicy.Type.CHANNEL_ID);
848     assertThat(policies.get(1).isTerminal()).isFalse();
849   }
850 
851   @Test
parseRouteAction_custerSpecifierNotSet()852   public void parseRouteAction_custerSpecifierNotSet() {
853     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
854         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
855             .build();
856     StructOrError<RouteAction> struct =
857         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
858             ImmutableMap.of(), ImmutableSet.of());
859     assertThat(struct).isNull();
860   }
861 
862   @Test
parseRouteAction_clusterSpecifier_routeLookupDisabled()863   public void parseRouteAction_clusterSpecifier_routeLookupDisabled() {
864     XdsResourceType.enableRouteLookup = false;
865     io.envoyproxy.envoy.config.route.v3.RouteAction proto =
866         io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
867             .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name())
868             .build();
869     StructOrError<RouteAction> struct =
870         XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry,
871             ImmutableMap.of(), ImmutableSet.of());
872     assertThat(struct).isNull();
873   }
874 
875   @Test
parseClusterWeight()876   public void parseClusterWeight() {
877     io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto =
878         io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight.newBuilder()
879             .setName("cluster-foo")
880             .setWeight(UInt32Value.newBuilder().setValue(30))
881             .build();
882     ClusterWeight clusterWeight =
883         XdsRouteConfigureResource.parseClusterWeight(proto, filterRegistry).getStruct();
884     assertThat(clusterWeight.name()).isEqualTo("cluster-foo");
885     assertThat(clusterWeight.weight()).isEqualTo(30);
886   }
887 
888   @Test
parseLocalityLbEndpoints_withHealthyEndpoints()889   public void parseLocalityLbEndpoints_withHealthyEndpoints() {
890     io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
891         io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
892             .setLocality(Locality.newBuilder()
893                 .setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
894             .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100))  // locality weight
895             .setPriority(1)
896             .addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
897                 .setEndpoint(Endpoint.newBuilder()
898                     .setAddress(Address.newBuilder()
899                         .setSocketAddress(
900                             SocketAddress.newBuilder()
901                                 .setAddress("172.14.14.5").setPortValue(8888))))
902                 .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY)
903                 .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20)))  // endpoint weight
904             .build();
905     StructOrError<LocalityLbEndpoints> struct = XdsEndpointResource.parseLocalityLbEndpoints(proto);
906     assertThat(struct.getErrorDetail()).isNull();
907     assertThat(struct.getStruct()).isEqualTo(
908         LocalityLbEndpoints.create(
909             Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
910   }
911 
912   @Test
parseLocalityLbEndpoints_treatUnknownHealthAsHealthy()913   public void parseLocalityLbEndpoints_treatUnknownHealthAsHealthy() {
914     io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
915         io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
916             .setLocality(Locality.newBuilder()
917                 .setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
918             .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100))  // locality weight
919             .setPriority(1)
920             .addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
921                 .setEndpoint(Endpoint.newBuilder()
922                     .setAddress(Address.newBuilder()
923                         .setSocketAddress(
924                             SocketAddress.newBuilder()
925                                 .setAddress("172.14.14.5").setPortValue(8888))))
926                 .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
927                 .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20)))  // endpoint weight
928             .build();
929     StructOrError<LocalityLbEndpoints> struct = XdsEndpointResource.parseLocalityLbEndpoints(proto);
930     assertThat(struct.getErrorDetail()).isNull();
931     assertThat(struct.getStruct()).isEqualTo(
932         LocalityLbEndpoints.create(
933             Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
934   }
935 
936   @Test
parseLocalityLbEndpoints_withUnHealthyEndpoints()937   public void parseLocalityLbEndpoints_withUnHealthyEndpoints() {
938     io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
939         io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
940             .setLocality(Locality.newBuilder()
941                 .setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
942             .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100))  // locality weight
943             .setPriority(1)
944             .addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
945                 .setEndpoint(Endpoint.newBuilder()
946                     .setAddress(Address.newBuilder()
947                         .setSocketAddress(
948                             SocketAddress.newBuilder()
949                                 .setAddress("172.14.14.5").setPortValue(8888))))
950                 .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNHEALTHY)
951                 .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20)))  // endpoint weight
952             .build();
953     StructOrError<LocalityLbEndpoints> struct = XdsEndpointResource.parseLocalityLbEndpoints(proto);
954     assertThat(struct.getErrorDetail()).isNull();
955     assertThat(struct.getStruct()).isEqualTo(
956         LocalityLbEndpoints.create(
957             Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, false)), 100, 1));
958   }
959 
960   @Test
parseLocalityLbEndpoints_ignorZeroWeightLocality()961   public void parseLocalityLbEndpoints_ignorZeroWeightLocality() {
962     io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
963         io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
964             .setLocality(Locality.newBuilder()
965                 .setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
966             .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(0))  // locality weight
967             .setPriority(1)
968             .addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
969                 .setEndpoint(Endpoint.newBuilder()
970                     .setAddress(Address.newBuilder()
971                         .setSocketAddress(
972                             SocketAddress.newBuilder()
973                                 .setAddress("172.14.14.5").setPortValue(8888))))
974                 .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
975                 .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20)))  // endpoint weight
976             .build();
977     assertThat(XdsEndpointResource.parseLocalityLbEndpoints(proto)).isNull();
978   }
979 
980   @Test
parseLocalityLbEndpoints_invalidPriority()981   public void parseLocalityLbEndpoints_invalidPriority() {
982     io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
983         io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
984             .setLocality(Locality.newBuilder()
985                 .setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
986             .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100))  // locality weight
987             .setPriority(-1)
988             .addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
989                 .setEndpoint(Endpoint.newBuilder()
990                     .setAddress(Address.newBuilder()
991                         .setSocketAddress(
992                             SocketAddress.newBuilder()
993                                 .setAddress("172.14.14.5").setPortValue(8888))))
994                 .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
995                 .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20)))  // endpoint weight
996             .build();
997     StructOrError<LocalityLbEndpoints> struct = XdsEndpointResource.parseLocalityLbEndpoints(proto);
998     assertThat(struct.getErrorDetail()).isEqualTo("negative priority");
999   }
1000 
1001   @Test
parseHttpFilter_unsupportedButOptional()1002   public void parseHttpFilter_unsupportedButOptional() {
1003     HttpFilter httpFilter = HttpFilter.newBuilder()
1004         .setIsOptional(true)
1005         .setTypedConfig(Any.pack(StringValue.of("unsupported")))
1006         .build();
1007     assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true)).isNull();
1008   }
1009 
1010   private static class SimpleFilterConfig implements FilterConfig {
1011     private final Message message;
1012 
SimpleFilterConfig(Message rawProtoMessage)1013     public SimpleFilterConfig(Message rawProtoMessage) {
1014       message = rawProtoMessage;
1015     }
1016 
getConfig()1017     public Message getConfig() {
1018       return message;
1019     }
1020 
1021     @Override
typeUrl()1022     public String typeUrl() {
1023       return null;
1024     }
1025   }
1026 
1027   private static class TestFilter implements io.grpc.xds.Filter,
1028       io.grpc.xds.Filter.ClientInterceptorBuilder {
1029     @Override
typeUrls()1030     public String[] typeUrls() {
1031       return new String[]{"test-url"};
1032     }
1033 
1034     @Override
parseFilterConfig(Message rawProtoMessage)1035     public ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage) {
1036       return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage));
1037     }
1038 
1039     @Override
parseFilterConfigOverride( Message rawProtoMessage)1040     public ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
1041         Message rawProtoMessage) {
1042       return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage));
1043     }
1044 
1045     @Nullable
1046     @Override
buildClientInterceptor(FilterConfig config, @Nullable FilterConfig overrideConfig, LoadBalancer.PickSubchannelArgs args, ScheduledExecutorService scheduler)1047     public ClientInterceptor buildClientInterceptor(FilterConfig config,
1048                                                     @Nullable FilterConfig overrideConfig,
1049                                                     LoadBalancer.PickSubchannelArgs args,
1050                                                     ScheduledExecutorService scheduler) {
1051       return null;
1052     }
1053   }
1054 
1055   @Test
parseHttpFilter_typedStructMigration()1056   public void parseHttpFilter_typedStructMigration() {
1057     filterRegistry.register(new TestFilter());
1058     Struct rawStruct = Struct.newBuilder()
1059         .putFields("name", Value.newBuilder().setStringValue("default").build())
1060         .build();
1061     HttpFilter httpFilter = HttpFilter.newBuilder()
1062         .setIsOptional(true)
1063         .setTypedConfig(Any.pack(
1064             com.github.udpa.udpa.type.v1.TypedStruct.newBuilder()
1065                 .setTypeUrl("test-url")
1066                 .setValue(rawStruct)
1067         .build())).build();
1068     FilterConfig config = XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry,
1069         true).getStruct();
1070     assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct);
1071 
1072     HttpFilter httpFilterNewTypeStruct = HttpFilter.newBuilder()
1073         .setIsOptional(true)
1074         .setTypedConfig(Any.pack(
1075             TypedStruct.newBuilder()
1076                 .setTypeUrl("test-url")
1077                 .setValue(rawStruct)
1078                 .build())).build();
1079     config = XdsListenerResource.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry,
1080         true).getStruct();
1081     assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct);
1082   }
1083 
1084   @Test
parseOverrideHttpFilter_typedStructMigration()1085   public void parseOverrideHttpFilter_typedStructMigration() {
1086     filterRegistry.register(new TestFilter());
1087     Struct rawStruct0 = Struct.newBuilder()
1088         .putFields("name", Value.newBuilder().setStringValue("default0").build())
1089         .build();
1090     Struct rawStruct1 = Struct.newBuilder()
1091         .putFields("name", Value.newBuilder().setStringValue("default1").build())
1092         .build();
1093     Map<String, Any> rawFilterMap = ImmutableMap.of(
1094         "struct-0", Any.pack(
1095             com.github.udpa.udpa.type.v1.TypedStruct.newBuilder()
1096                 .setTypeUrl("test-url")
1097                 .setValue(rawStruct0)
1098                 .build()),
1099           "struct-1", Any.pack(
1100               TypedStruct.newBuilder()
1101                   .setTypeUrl("test-url")
1102                   .setValue(rawStruct1)
1103                   .build())
1104     );
1105     Map<String, FilterConfig> map = XdsRouteConfigureResource.parseOverrideFilterConfigs(
1106         rawFilterMap, filterRegistry).getStruct();
1107     assertThat(((SimpleFilterConfig)map.get("struct-0")).getConfig()).isEqualTo(rawStruct0);
1108     assertThat(((SimpleFilterConfig)map.get("struct-1")).getConfig()).isEqualTo(rawStruct1);
1109   }
1110 
1111   @Test
parseHttpFilter_unsupportedAndRequired()1112   public void parseHttpFilter_unsupportedAndRequired() {
1113     HttpFilter httpFilter = HttpFilter.newBuilder()
1114         .setIsOptional(false)
1115         .setName("unsupported.filter")
1116         .setTypedConfig(Any.pack(StringValue.of("string value")))
1117         .build();
1118     assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true)
1119         .getErrorDetail()).isEqualTo(
1120             "HttpFilter [unsupported.filter]"
1121                 + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported "
1122                 + "for client");
1123   }
1124 
1125   @Test
parseHttpFilter_routerFilterForClient()1126   public void parseHttpFilter_routerFilterForClient() {
1127     filterRegistry.register(RouterFilter.INSTANCE);
1128     HttpFilter httpFilter =
1129         HttpFilter.newBuilder()
1130             .setIsOptional(false)
1131             .setName("envoy.router")
1132             .setTypedConfig(Any.pack(Router.getDefaultInstance()))
1133             .build();
1134     FilterConfig config = XdsListenerResource.parseHttpFilter(
1135         httpFilter, filterRegistry, true /* isForClient */).getStruct();
1136     assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL);
1137   }
1138 
1139   @Test
parseHttpFilter_routerFilterForServer()1140   public void parseHttpFilter_routerFilterForServer() {
1141     filterRegistry.register(RouterFilter.INSTANCE);
1142     HttpFilter httpFilter =
1143         HttpFilter.newBuilder()
1144             .setIsOptional(false)
1145             .setName("envoy.router")
1146             .setTypedConfig(Any.pack(Router.getDefaultInstance()))
1147             .build();
1148     FilterConfig config = XdsListenerResource.parseHttpFilter(
1149         httpFilter, filterRegistry, false /* isForClient */).getStruct();
1150     assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL);
1151   }
1152 
1153   @Test
parseHttpFilter_faultConfigForClient()1154   public void parseHttpFilter_faultConfigForClient() {
1155     filterRegistry.register(FaultFilter.INSTANCE);
1156     HttpFilter httpFilter =
1157         HttpFilter.newBuilder()
1158             .setIsOptional(false)
1159             .setName("envoy.fault")
1160             .setTypedConfig(
1161                 Any.pack(
1162                     HTTPFault.newBuilder()
1163                         .setDelay(
1164                             FaultDelay.newBuilder()
1165                                 .setFixedDelay(Durations.fromNanos(1234L)))
1166                         .setAbort(
1167                             FaultAbort.newBuilder()
1168                                 .setHttpStatus(300)
1169                                 .setPercentage(
1170                                     FractionalPercent.newBuilder()
1171                                         .setNumerator(10)
1172                                         .setDenominator(DenominatorType.HUNDRED)))
1173                         .build()))
1174             .build();
1175     FilterConfig config = XdsListenerResource.parseHttpFilter(
1176         httpFilter, filterRegistry, true /* isForClient */).getStruct();
1177     assertThat(config).isInstanceOf(FaultConfig.class);
1178   }
1179 
1180   @Test
parseHttpFilter_faultConfigUnsupportedForServer()1181   public void parseHttpFilter_faultConfigUnsupportedForServer() {
1182     filterRegistry.register(FaultFilter.INSTANCE);
1183     HttpFilter httpFilter =
1184         HttpFilter.newBuilder()
1185             .setIsOptional(false)
1186             .setName("envoy.fault")
1187             .setTypedConfig(
1188                 Any.pack(
1189                     HTTPFault.newBuilder()
1190                         .setDelay(
1191                             FaultDelay.newBuilder()
1192                                 .setFixedDelay(Durations.fromNanos(1234L)))
1193                         .setAbort(
1194                             FaultAbort.newBuilder()
1195                                 .setHttpStatus(300)
1196                                 .setPercentage(
1197                                     FractionalPercent.newBuilder()
1198                                         .setNumerator(10)
1199                                         .setDenominator(DenominatorType.HUNDRED)))
1200                         .build()))
1201             .build();
1202     StructOrError<FilterConfig> config =
1203         XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */);
1204     assertThat(config.getErrorDetail()).isEqualTo(
1205         "HttpFilter [envoy.fault](" + FaultFilter.TYPE_URL + ") is required but "
1206             + "unsupported for server");
1207   }
1208 
1209   @Test
parseHttpFilter_rbacConfigForServer()1210   public void parseHttpFilter_rbacConfigForServer() {
1211     filterRegistry.register(RbacFilter.INSTANCE);
1212     HttpFilter httpFilter =
1213         HttpFilter.newBuilder()
1214             .setIsOptional(false)
1215             .setName("envoy.auth")
1216             .setTypedConfig(
1217                 Any.pack(
1218                     io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
1219                         .setRules(
1220                             RBAC.newBuilder()
1221                                 .setAction(Action.ALLOW)
1222                                 .putPolicies(
1223                                     "allow-all",
1224                                     Policy.newBuilder()
1225                                         .addPrincipals(Principal.newBuilder().setAny(true))
1226                                         .addPermissions(Permission.newBuilder().setAny(true))
1227                                         .build())
1228                                 .build())
1229                         .build()))
1230             .build();
1231     FilterConfig config = XdsListenerResource.parseHttpFilter(
1232         httpFilter, filterRegistry, false /* isForClient */).getStruct();
1233     assertThat(config).isInstanceOf(RbacConfig.class);
1234   }
1235 
1236   @Test
parseHttpFilter_rbacConfigUnsupportedForClient()1237   public void parseHttpFilter_rbacConfigUnsupportedForClient() {
1238     filterRegistry.register(RbacFilter.INSTANCE);
1239     HttpFilter httpFilter =
1240         HttpFilter.newBuilder()
1241             .setIsOptional(false)
1242             .setName("envoy.auth")
1243             .setTypedConfig(
1244                 Any.pack(
1245                     io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
1246                         .setRules(
1247                             RBAC.newBuilder()
1248                                 .setAction(Action.ALLOW)
1249                                 .putPolicies(
1250                                     "allow-all",
1251                                     Policy.newBuilder()
1252                                         .addPrincipals(Principal.newBuilder().setAny(true))
1253                                         .addPermissions(Permission.newBuilder().setAny(true))
1254                                         .build())
1255                                 .build())
1256                         .build()))
1257             .build();
1258     StructOrError<FilterConfig> config =
1259         XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */);
1260     assertThat(config.getErrorDetail()).isEqualTo(
1261         "HttpFilter [envoy.auth](" + RbacFilter.TYPE_URL + ") is required but "
1262             + "unsupported for client");
1263   }
1264 
1265   @Test
parseOverrideRbacFilterConfig()1266   public void parseOverrideRbacFilterConfig() {
1267     filterRegistry.register(RbacFilter.INSTANCE);
1268     RBACPerRoute rbacPerRoute =
1269         RBACPerRoute.newBuilder()
1270             .setRbac(
1271                 io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
1272                     .setRules(
1273                         RBAC.newBuilder()
1274                             .setAction(Action.ALLOW)
1275                             .putPolicies(
1276                                 "allow-all",
1277                                 Policy.newBuilder()
1278                                     .addPrincipals(Principal.newBuilder().setAny(true))
1279                                     .addPermissions(Permission.newBuilder().setAny(true))
1280                             .build())))
1281             .build();
1282     Map<String, Any> configOverrides = ImmutableMap.of("envoy.auth", Any.pack(rbacPerRoute));
1283     Map<String, FilterConfig> parsedConfigs =
1284         XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry)
1285             .getStruct();
1286     assertThat(parsedConfigs).hasSize(1);
1287     assertThat(parsedConfigs).containsKey("envoy.auth");
1288     assertThat(parsedConfigs.get("envoy.auth")).isInstanceOf(RbacConfig.class);
1289   }
1290 
1291   @Test
parseOverrideFilterConfigs_unsupportedButOptional()1292   public void parseOverrideFilterConfigs_unsupportedButOptional() {
1293     filterRegistry.register(FaultFilter.INSTANCE);
1294     HTTPFault httpFault = HTTPFault.newBuilder()
1295         .setDelay(FaultDelay.newBuilder().setFixedDelay(Durations.fromNanos(3000)))
1296         .build();
1297     Map<String, Any> configOverrides = ImmutableMap.of(
1298         "envoy.fault",
1299         Any.pack(httpFault),
1300         "unsupported.filter",
1301         Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder()
1302             .setIsOptional(true).setConfig(Any.pack(StringValue.of("string value")))
1303             .build()));
1304     Map<String, FilterConfig> parsedConfigs =
1305         XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry)
1306             .getStruct();
1307     assertThat(parsedConfigs).hasSize(1);
1308     assertThat(parsedConfigs).containsKey("envoy.fault");
1309   }
1310 
1311   @Test
parseOverrideFilterConfigs_unsupportedAndRequired()1312   public void parseOverrideFilterConfigs_unsupportedAndRequired() {
1313     filterRegistry.register(FaultFilter.INSTANCE);
1314     HTTPFault httpFault = HTTPFault.newBuilder()
1315         .setDelay(FaultDelay.newBuilder().setFixedDelay(Durations.fromNanos(3000)))
1316         .build();
1317     Map<String, Any> configOverrides = ImmutableMap.of(
1318         "envoy.fault",
1319         Any.pack(httpFault),
1320         "unsupported.filter",
1321         Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder()
1322             .setIsOptional(false).setConfig(Any.pack(StringValue.of("string value")))
1323             .build()));
1324     assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry)
1325         .getErrorDetail()).isEqualTo(
1326             "HttpFilter [unsupported.filter]"
1327                 + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported");
1328 
1329     configOverrides = ImmutableMap.of(
1330         "envoy.fault",
1331         Any.pack(httpFault),
1332         "unsupported.filter",
1333         Any.pack(StringValue.of("string value")));
1334     assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry)
1335         .getErrorDetail()).isEqualTo(
1336             "HttpFilter [unsupported.filter]"
1337                 + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported");
1338   }
1339 
1340   @Test
parseHttpConnectionManager_xffNumTrustedHopsUnsupported()1341   public void parseHttpConnectionManager_xffNumTrustedHopsUnsupported()
1342       throws ResourceInvalidException {
1343     @SuppressWarnings("deprecation")
1344     HttpConnectionManager hcm = HttpConnectionManager.newBuilder().setXffNumTrustedHops(2).build();
1345     thrown.expect(ResourceInvalidException.class);
1346     thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported");
1347     XdsListenerResource.parseHttpConnectionManager(
1348         hcm, filterRegistry,
1349         true /* does not matter */);
1350   }
1351 
1352   @Test
parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty()1353   public void parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty()
1354       throws ResourceInvalidException {
1355     @SuppressWarnings("deprecation")
1356     HttpConnectionManager hcm = HttpConnectionManager.newBuilder()
1357         .addOriginalIpDetectionExtensions(TypedExtensionConfig.newBuilder().build())
1358         .build();
1359     thrown.expect(ResourceInvalidException.class);
1360     thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported");
1361     XdsListenerResource.parseHttpConnectionManager(
1362         hcm, filterRegistry, false);
1363   }
1364 
1365   @Test
parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration()1366   public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration()
1367       throws ResourceInvalidException {
1368     HttpConnectionManager hcm =
1369         HttpConnectionManager.newBuilder()
1370             .setCommonHttpProtocolOptions(
1371                 HttpProtocolOptions.newBuilder()
1372                     .setMaxStreamDuration(Durations.fromNanos(1000L)))
1373             .addHttpFilters(
1374                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1375                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1376             .build();
1377     thrown.expect(ResourceInvalidException.class);
1378     thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS");
1379     XdsListenerResource.parseHttpConnectionManager(
1380         hcm, filterRegistry,
1381         true /* does not matter */);
1382   }
1383 
1384   @Test
parseHttpConnectionManager_duplicateHttpFilters()1385   public void parseHttpConnectionManager_duplicateHttpFilters() throws ResourceInvalidException {
1386     HttpConnectionManager hcm =
1387         HttpConnectionManager.newBuilder()
1388             .addHttpFilters(
1389                 HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
1390             .addHttpFilters(
1391                 HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
1392             .addHttpFilters(
1393                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1394                         Any.pack(Router.newBuilder().build())).setIsOptional(true))
1395             .build();
1396     thrown.expect(ResourceInvalidException.class);
1397     thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo");
1398     XdsListenerResource.parseHttpConnectionManager(
1399         hcm, filterRegistry,
1400         true /* does not matter */);
1401   }
1402 
1403   @Test
parseHttpConnectionManager_lastNotTerminal()1404   public void parseHttpConnectionManager_lastNotTerminal() throws ResourceInvalidException {
1405     filterRegistry.register(FaultFilter.INSTANCE);
1406     HttpConnectionManager hcm =
1407           HttpConnectionManager.newBuilder()
1408               .addHttpFilters(
1409                 HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
1410               .addHttpFilters(
1411                 HttpFilter.newBuilder().setName("envoy.filter.bar").setIsOptional(true)
1412                     .setTypedConfig(Any.pack(HTTPFault.newBuilder().build())))
1413                     .build();
1414     thrown.expect(ResourceInvalidException.class);
1415     thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar");
1416     XdsListenerResource.parseHttpConnectionManager(
1417             hcm, filterRegistry,
1418             true /* does not matter */);
1419   }
1420 
1421   @Test
parseHttpConnectionManager_terminalNotLast()1422   public void parseHttpConnectionManager_terminalNotLast() throws ResourceInvalidException {
1423     filterRegistry.register(RouterFilter.INSTANCE);
1424     HttpConnectionManager hcm =
1425             HttpConnectionManager.newBuilder()
1426                     .addHttpFilters(
1427                             HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1428                                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1429                     .addHttpFilters(
1430                             HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
1431                     .build();
1432     thrown.expect(ResourceInvalidException.class);
1433     thrown.expectMessage("A terminal HttpFilter must be the last filter: terminal");
1434     XdsListenerResource.parseHttpConnectionManager(
1435             hcm, filterRegistry,
1436             true);
1437   }
1438 
1439   @Test
parseHttpConnectionManager_unknownFilters()1440   public void parseHttpConnectionManager_unknownFilters() throws ResourceInvalidException {
1441     HttpConnectionManager hcm =
1442             HttpConnectionManager.newBuilder()
1443                     .addHttpFilters(
1444                             HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
1445                     .addHttpFilters(
1446                             HttpFilter.newBuilder().setName("envoy.filter.bar").setIsOptional(true))
1447                     .build();
1448     thrown.expect(ResourceInvalidException.class);
1449     thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar");
1450     XdsListenerResource.parseHttpConnectionManager(
1451             hcm, filterRegistry,
1452             true /* does not matter */);
1453   }
1454 
1455   @Test
parseHttpConnectionManager_emptyFilters()1456   public void parseHttpConnectionManager_emptyFilters() throws ResourceInvalidException {
1457     HttpConnectionManager hcm =
1458             HttpConnectionManager.newBuilder()
1459                     .build();
1460     thrown.expect(ResourceInvalidException.class);
1461     thrown.expectMessage("Missing HttpFilter in HttpConnectionManager.");
1462     XdsListenerResource.parseHttpConnectionManager(
1463             hcm, filterRegistry,
1464             true /* does not matter */);
1465   }
1466 
1467   @Test
parseHttpConnectionManager_clusterSpecifierPlugin()1468   public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception {
1469     XdsResourceType.enableRouteLookup = true;
1470     RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder()
1471         .addGrpcKeybuilders(
1472             GrpcKeyBuilder.newBuilder()
1473                 .addNames(Name.newBuilder().setService("service1"))
1474                 .addNames(Name.newBuilder().setService("service2"))
1475                 .addHeaders(
1476                     NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true)))
1477         .setLookupService("rls-cbt.googleapis.com")
1478         .setLookupServiceTimeout(Durations.fromMillis(1234))
1479         .setCacheSizeBytes(5000)
1480         .addValidTargets("valid-target")
1481         .build();
1482     RouteLookupClusterSpecifier specifier =
1483         RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig).build();
1484     TypedExtensionConfig typedExtensionConfig = TypedExtensionConfig.newBuilder()
1485         .setName("rls-plugin-1")
1486         .setTypedConfig(Any.pack(specifier))
1487         .build();
1488     io.envoyproxy.envoy.config.route.v3.Route route =
1489         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
1490             .setName("route-1")
1491             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix(""))
1492             .setRoute(io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
1493                 .setClusterSpecifierPlugin("rls-plugin-1"))
1494             .build();
1495     HttpConnectionManager hcm =
1496         HttpConnectionManager.newBuilder()
1497             .setRouteConfig(
1498                 RouteConfiguration.newBuilder()
1499                     .addClusterSpecifierPlugins(
1500                         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1501                             .setExtension(typedExtensionConfig)
1502                             .build())
1503                     .addVirtualHosts(io.envoyproxy.envoy.config.route.v3.VirtualHost.newBuilder()
1504                         .setName("virtual-host-1")
1505                         .addRoutes(route)))
1506             .addHttpFilters(
1507                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1508                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1509             .build();
1510 
1511     io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager(
1512         hcm, filterRegistry,
1513         true /* does not matter */);
1514 
1515     VirtualHost virtualHost = Iterables.getOnlyElement(parsedHcm.virtualHosts());
1516     Route parsedRoute = Iterables.getOnlyElement(virtualHost.routes());
1517     NamedPluginConfig namedPluginConfig =
1518         parsedRoute.routeAction().namedClusterSpecifierPluginConfig();
1519     assertThat(namedPluginConfig.name()).isEqualTo("rls-plugin-1");
1520     assertThat(namedPluginConfig.config()).isInstanceOf(RlsPluginConfig.class);
1521   }
1522 
1523   @Test
parseHttpConnectionManager_duplicatePluginName()1524   public void parseHttpConnectionManager_duplicatePluginName() throws Exception {
1525     XdsResourceType.enableRouteLookup = true;
1526     RouteLookupConfig routeLookupConfig1 = RouteLookupConfig.newBuilder()
1527         .addGrpcKeybuilders(
1528             GrpcKeyBuilder.newBuilder()
1529                 .addNames(Name.newBuilder().setService("service1"))
1530                 .addNames(Name.newBuilder().setService("service2"))
1531                 .addHeaders(
1532                     NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true)))
1533         .setLookupService("rls-cbt.googleapis.com")
1534         .setLookupServiceTimeout(Durations.fromMillis(1234))
1535         .setCacheSizeBytes(5000)
1536         .addValidTargets("valid-target")
1537         .build();
1538     RouteLookupClusterSpecifier specifier1 =
1539         RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig1).build();
1540     RouteLookupConfig routeLookupConfig2 = RouteLookupConfig.newBuilder()
1541         .addGrpcKeybuilders(
1542             GrpcKeyBuilder.newBuilder()
1543                 .addNames(Name.newBuilder().setService("service3"))
1544                 .addHeaders(
1545                     NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true)))
1546         .setLookupService("rls-cbt.googleapis.com")
1547         .setLookupServiceTimeout(Durations.fromMillis(1234))
1548         .setCacheSizeBytes(5000)
1549         .addValidTargets("valid-target")
1550         .build();
1551     RouteLookupClusterSpecifier specifier2 =
1552         RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig2).build();
1553     TypedExtensionConfig typedExtensionConfig = TypedExtensionConfig.newBuilder()
1554         .setName("rls-plugin-1")
1555         .setTypedConfig(Any.pack(specifier1))
1556         .build();
1557     TypedExtensionConfig typedExtensionConfig2 = TypedExtensionConfig.newBuilder()
1558         .setName("rls-plugin-1")
1559         .setTypedConfig(Any.pack(specifier2))
1560         .build();
1561     io.envoyproxy.envoy.config.route.v3.Route route =
1562         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
1563             .setName("route-1")
1564             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix(""))
1565             .setRoute(io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
1566                 .setClusterSpecifierPlugin("rls-plugin-1"))
1567             .build();
1568     HttpConnectionManager hcm =
1569         HttpConnectionManager.newBuilder()
1570             .setRouteConfig(
1571                 RouteConfiguration.newBuilder()
1572                     .addClusterSpecifierPlugins(
1573                         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1574                             .setExtension(typedExtensionConfig)
1575                             .build())
1576                     .addClusterSpecifierPlugins(
1577                         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1578                             .setExtension(typedExtensionConfig2)
1579                             .build())
1580                     .addVirtualHosts(io.envoyproxy.envoy.config.route.v3.VirtualHost.newBuilder()
1581                         .setName("virtual-host-1")
1582                         .addRoutes(route)))
1583             .addHttpFilters(
1584                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1585                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1586             .build();
1587 
1588     thrown.expect(ResourceInvalidException.class);
1589     thrown.expectMessage("Multiple ClusterSpecifierPlugins with the same name: rls-plugin-1");
1590 
1591     XdsListenerResource.parseHttpConnectionManager(
1592         hcm, filterRegistry,
1593         true /* does not matter */);
1594   }
1595 
1596   @Test
parseHttpConnectionManager_pluginNameNotFound()1597   public void parseHttpConnectionManager_pluginNameNotFound() throws Exception {
1598     XdsResourceType.enableRouteLookup = true;
1599     RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder()
1600         .addGrpcKeybuilders(
1601             GrpcKeyBuilder.newBuilder()
1602                 .addNames(Name.newBuilder().setService("service1"))
1603                 .addNames(Name.newBuilder().setService("service2"))
1604                 .addHeaders(
1605                     NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true)))
1606         .setLookupService("rls-cbt.googleapis.com")
1607         .setLookupServiceTimeout(Durations.fromMillis(1234))
1608         .setCacheSizeBytes(5000)
1609         .addValidTargets("valid-target")
1610         .build();
1611     RouteLookupClusterSpecifier specifier =
1612         RouteLookupClusterSpecifier.newBuilder().setRouteLookupConfig(routeLookupConfig).build();
1613     TypedExtensionConfig typedExtensionConfig = TypedExtensionConfig.newBuilder()
1614         .setName("rls-plugin-1")
1615         .setTypedConfig(Any.pack(specifier))
1616         .build();
1617     io.envoyproxy.envoy.config.route.v3.Route route =
1618         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
1619             .setName("route-1")
1620             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix(""))
1621             .setRoute(io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
1622                 .setClusterSpecifierPlugin("invalid-plugin-name"))
1623             .build();
1624     HttpConnectionManager hcm =
1625         HttpConnectionManager.newBuilder()
1626             .setRouteConfig(
1627                 RouteConfiguration.newBuilder()
1628                     .addClusterSpecifierPlugins(
1629                         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1630                             .setExtension(typedExtensionConfig)
1631                             .build())
1632                     .addVirtualHosts(io.envoyproxy.envoy.config.route.v3.VirtualHost.newBuilder()
1633                         .setName("virtual-host-1")
1634                         .addRoutes(route)))
1635             .addHttpFilters(
1636                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1637                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1638             .build();
1639 
1640     thrown.expect(ResourceInvalidException.class);
1641     thrown.expectMessage("ClusterSpecifierPlugin for [invalid-plugin-name] not found");
1642 
1643     XdsListenerResource.parseHttpConnectionManager(
1644         hcm, filterRegistry,
1645         true /* does not matter */);
1646   }
1647 
1648 
1649   @Test
parseHttpConnectionManager_optionalPlugin()1650   public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidException {
1651     XdsResourceType.enableRouteLookup = true;
1652 
1653     // RLS Plugin, and a route to it.
1654     RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder()
1655         .addGrpcKeybuilders(
1656             GrpcKeyBuilder.newBuilder()
1657                 .addNames(Name.newBuilder().setService("service1"))
1658                 .addNames(Name.newBuilder().setService("service2"))
1659                 .addHeaders(
1660                     NameMatcher.newBuilder().setKey("key1").addNames("v1").setRequiredMatch(true)))
1661         .setLookupService("rls-cbt.googleapis.com")
1662         .setLookupServiceTimeout(Durations.fromMillis(1234))
1663         .setCacheSizeBytes(5000)
1664         .addValidTargets("valid-target")
1665         .build();
1666     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin rlsPlugin =
1667         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1668             .setExtension(
1669                 TypedExtensionConfig.newBuilder()
1670                     .setName("rls-plugin-1")
1671                     .setTypedConfig(Any.pack(
1672                         RouteLookupClusterSpecifier.newBuilder()
1673                             .setRouteLookupConfig(routeLookupConfig)
1674                             .build())))
1675             .build();
1676     io.envoyproxy.envoy.config.route.v3.Route rlsRoute =
1677         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
1678             .setName("rls-route-1")
1679             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix(""))
1680             .setRoute(io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
1681                 .setClusterSpecifierPlugin("rls-plugin-1"))
1682             .build();
1683 
1684     // Unknown optional plugin, and a route to it.
1685     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin optionalPlugin =
1686         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1687             .setIsOptional(true)
1688             .setExtension(
1689                 TypedExtensionConfig.newBuilder()
1690                     .setName("optional-plugin-1")
1691                     .setTypedConfig(Any.pack(StringValue.of("unregistered")))
1692                     .build())
1693             .build();
1694     io.envoyproxy.envoy.config.route.v3.Route optionalRoute =
1695         io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
1696             .setName("optional-route-1")
1697             .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix(""))
1698             .setRoute(io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
1699                 .setClusterSpecifierPlugin("optional-plugin-1"))
1700             .build();
1701 
1702 
1703     // Build and parse the route.
1704     RouteConfiguration routeConfig = RouteConfiguration.newBuilder()
1705         .addClusterSpecifierPlugins(rlsPlugin)
1706         .addClusterSpecifierPlugins(optionalPlugin)
1707         .addVirtualHosts(
1708             io.envoyproxy.envoy.config.route.v3.VirtualHost.newBuilder()
1709                 .setName("virtual-host-1")
1710                 .addRoutes(rlsRoute)
1711                 .addRoutes(optionalRoute))
1712         .build();
1713     io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager(
1714         HttpConnectionManager.newBuilder().setRouteConfig(routeConfig)
1715             .addHttpFilters(
1716                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1717                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1718             .build(), filterRegistry,
1719         true /* does not matter */);
1720 
1721     // Verify that the only route left is the one with the registered RLS plugin `rls-plugin-1`,
1722     // while the route with unregistered optional `optional-plugin-`1 has been skipped.
1723     VirtualHost virtualHost = Iterables.getOnlyElement(parsedHcm.virtualHosts());
1724     Route parsedRoute = Iterables.getOnlyElement(virtualHost.routes());
1725     NamedPluginConfig namedPluginConfig =
1726         parsedRoute.routeAction().namedClusterSpecifierPluginConfig();
1727     assertThat(namedPluginConfig.name()).isEqualTo("rls-plugin-1");
1728     assertThat(namedPluginConfig.config()).isInstanceOf(RlsPluginConfig.class);
1729   }
1730 
1731   @Test
parseHttpConnectionManager_validateRdsConfigSource()1732   public void parseHttpConnectionManager_validateRdsConfigSource() throws Exception {
1733     XdsResourceType.enableRouteLookup = true;
1734 
1735     HttpConnectionManager hcm1 =
1736         HttpConnectionManager.newBuilder()
1737             .setRds(Rds.newBuilder()
1738                 .setRouteConfigName("rds-config-foo")
1739                 .setConfigSource(
1740                     ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())))
1741             .addHttpFilters(
1742                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1743                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1744             .build();
1745     XdsListenerResource.parseHttpConnectionManager(
1746         hcm1, filterRegistry,
1747         true /* does not matter */);
1748 
1749     HttpConnectionManager hcm2 =
1750         HttpConnectionManager.newBuilder()
1751             .setRds(Rds.newBuilder()
1752                 .setRouteConfigName("rds-config-foo")
1753                 .setConfigSource(
1754                     ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance())))
1755             .addHttpFilters(
1756                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1757                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1758             .build();
1759     XdsListenerResource.parseHttpConnectionManager(
1760         hcm2, filterRegistry,
1761         true /* does not matter */);
1762 
1763     HttpConnectionManager hcm3 =
1764         HttpConnectionManager.newBuilder()
1765             .setRds(Rds.newBuilder()
1766                 .setRouteConfigName("rds-config-foo")
1767                 .setConfigSource(
1768                     ConfigSource.newBuilder()
1769                         .setPathConfigSource(PathConfigSource.newBuilder().setPath("foo-path"))))
1770             .addHttpFilters(
1771                 HttpFilter.newBuilder().setName("terminal").setTypedConfig(
1772                     Any.pack(Router.newBuilder().build())).setIsOptional(true))
1773             .build();
1774     thrown.expect(ResourceInvalidException.class);
1775     thrown.expectMessage(
1776         "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource");
1777     XdsListenerResource.parseHttpConnectionManager(
1778         hcm3, filterRegistry,
1779         true /* does not matter */);
1780   }
1781 
1782   @Test
parseClusterSpecifierPlugin_typedStructInTypedExtension()1783   public void parseClusterSpecifierPlugin_typedStructInTypedExtension() throws Exception {
1784     class TestPluginConfig implements PluginConfig {
1785       @Override
1786       public String typeUrl() {
1787         return "type.googleapis.com/google.protobuf.Empty";
1788       }
1789     }
1790 
1791     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1792     registry.register(new ClusterSpecifierPlugin() {
1793       @Override
1794       public String[] typeUrls() {
1795         return new String[] {
1796             "type.googleapis.com/google.protobuf.Empty",
1797         };
1798       }
1799 
1800       @Override
1801       public ConfigOrError<? extends PluginConfig> parsePlugin(Message rawProtoMessage) {
1802         return ConfigOrError.fromConfig(new TestPluginConfig());
1803       }
1804     });
1805 
1806     TypedStruct typedStruct = TypedStruct.newBuilder()
1807         .setTypeUrl("type.googleapis.com/google.protobuf.Empty")
1808         .setValue(Struct.newBuilder())
1809         .build();
1810     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin pluginProto =
1811         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1812             .setExtension(TypedExtensionConfig.newBuilder()
1813                 .setTypedConfig(Any.pack(typedStruct)))
1814             .build();
1815 
1816     PluginConfig pluginConfig = XdsRouteConfigureResource
1817         .parseClusterSpecifierPlugin(pluginProto, registry);
1818     assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class);
1819   }
1820 
1821   @Test
parseClusterSpecifierPlugin_v3TypedStructInTypedExtension()1822   public void parseClusterSpecifierPlugin_v3TypedStructInTypedExtension() throws Exception {
1823     class TestPluginConfig implements PluginConfig {
1824       @Override
1825       public String typeUrl() {
1826         return "type.googleapis.com/google.protobuf.Empty";
1827       }
1828     }
1829 
1830     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1831     registry.register(new ClusterSpecifierPlugin() {
1832       @Override
1833       public String[] typeUrls() {
1834         return new String[] {
1835             "type.googleapis.com/google.protobuf.Empty",
1836         };
1837       }
1838 
1839       @Override
1840       public ConfigOrError<? extends PluginConfig> parsePlugin(Message rawProtoMessage) {
1841         return ConfigOrError.fromConfig(new TestPluginConfig());
1842       }
1843     });
1844 
1845     com.github.xds.type.v3.TypedStruct typedStruct = com.github.xds.type.v3.TypedStruct.newBuilder()
1846         .setTypeUrl("type.googleapis.com/google.protobuf.Empty")
1847         .setValue(Struct.newBuilder())
1848         .build();
1849     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin pluginProto =
1850         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1851             .setExtension(TypedExtensionConfig.newBuilder()
1852                 .setTypedConfig(Any.pack(typedStruct)))
1853             .build();
1854 
1855     PluginConfig pluginConfig = XdsRouteConfigureResource
1856         .parseClusterSpecifierPlugin(pluginProto, registry);
1857     assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class);
1858   }
1859 
1860   @Test
parseClusterSpecifierPlugin_unregisteredPlugin()1861   public void parseClusterSpecifierPlugin_unregisteredPlugin() throws Exception {
1862     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1863     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin pluginProto =
1864         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1865             .setExtension(TypedExtensionConfig.newBuilder()
1866                 .setTypedConfig(Any.pack(StringValue.of("unregistered"))))
1867             .build();
1868 
1869     thrown.expect(ResourceInvalidException.class);
1870     thrown.expectMessage(
1871         "Unsupported ClusterSpecifierPlugin type: type.googleapis.com/google.protobuf.StringValue");
1872 
1873     XdsRouteConfigureResource.parseClusterSpecifierPlugin(pluginProto, registry);
1874   }
1875 
1876   @Test
parseClusterSpecifierPlugin_unregisteredPlugin_optional()1877   public void parseClusterSpecifierPlugin_unregisteredPlugin_optional()
1878       throws ResourceInvalidException {
1879     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1880     io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin pluginProto =
1881         io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1882             .setExtension(TypedExtensionConfig.newBuilder()
1883                 .setTypedConfig(Any.pack(StringValue.of("unregistered"))))
1884             .setIsOptional(true)
1885             .build();
1886 
1887     PluginConfig pluginConfig = XdsRouteConfigureResource
1888         .parseClusterSpecifierPlugin(pluginProto, registry);
1889     assertThat(pluginConfig).isNull();
1890   }
1891 
1892   @Test
parseClusterSpecifierPlugin_brokenPlugin()1893   public void parseClusterSpecifierPlugin_brokenPlugin() {
1894     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1895 
1896     Any failingAny = Any.newBuilder()
1897         .setTypeUrl("type.googleapis.com/xds.type.v3.TypedStruct")
1898         .setValue(ByteString.copyFromUtf8("fail"))
1899         .build();
1900 
1901     TypedExtensionConfig brokenPlugin = TypedExtensionConfig.newBuilder()
1902           .setName("bad-plugin-1")
1903           .setTypedConfig(failingAny)
1904           .build();
1905 
1906     try {
1907       XdsRouteConfigureResource.parseClusterSpecifierPlugin(
1908           io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1909               .setExtension(brokenPlugin)
1910               .build(),
1911           registry);
1912       fail("Expected ResourceInvalidException");
1913     } catch (ResourceInvalidException e) {
1914       assertThat(e).hasMessageThat()
1915           .startsWith("ClusterSpecifierPlugin [bad-plugin-1] contains invalid proto");
1916     }
1917   }
1918 
1919   @Test
parseClusterSpecifierPlugin_brokenPlugin_optional()1920   public void parseClusterSpecifierPlugin_brokenPlugin_optional() {
1921     ClusterSpecifierPluginRegistry registry = ClusterSpecifierPluginRegistry.newRegistry();
1922 
1923     Any failingAny = Any.newBuilder()
1924         .setTypeUrl("type.googleapis.com/xds.type.v3.TypedStruct")
1925         .setValue(ByteString.copyFromUtf8("fail"))
1926         .build();
1927 
1928     TypedExtensionConfig brokenPlugin = TypedExtensionConfig.newBuilder()
1929           .setName("bad-plugin-1")
1930           .setTypedConfig(failingAny)
1931           .build();
1932 
1933     // Despite being optional, still should fail.
1934     try {
1935       XdsRouteConfigureResource.parseClusterSpecifierPlugin(
1936           io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder()
1937               .setIsOptional(true)
1938               .setExtension(brokenPlugin)
1939               .build(),
1940           registry);
1941       fail("Expected ResourceInvalidException");
1942     } catch (ResourceInvalidException e) {
1943       assertThat(e).hasMessageThat()
1944           .startsWith("ClusterSpecifierPlugin [bad-plugin-1] contains invalid proto");
1945     }
1946   }
1947 
1948   @Test
parseCluster_ringHashLbPolicy_defaultLbConfig()1949   public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInvalidException {
1950     Cluster cluster = Cluster.newBuilder()
1951         .setName("cluster-foo.googleapis.com")
1952         .setType(DiscoveryType.EDS)
1953         .setEdsClusterConfig(
1954             EdsClusterConfig.newBuilder()
1955                 .setEdsConfig(
1956                     ConfigSource.newBuilder()
1957                         .setAds(AggregatedConfigSource.getDefaultInstance()))
1958                 .setServiceName("service-foo.googleapis.com"))
1959         .setLbPolicy(LbPolicy.RING_HASH)
1960         .build();
1961 
1962     CdsUpdate update = XdsClusterResource.processCluster(
1963         cluster, null, LRS_SERVER_INFO,
1964         LoadBalancerRegistry.getDefaultRegistry());
1965     LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig());
1966     assertThat(lbConfig.getPolicyName()).isEqualTo("ring_hash_experimental");
1967   }
1968 
1969   @Test
parseCluster_leastRequestLbPolicy_defaultLbConfig()1970   public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceInvalidException {
1971     XdsResourceType.enableLeastRequest = true;
1972     Cluster cluster = Cluster.newBuilder()
1973         .setName("cluster-foo.googleapis.com")
1974         .setType(DiscoveryType.EDS)
1975         .setEdsClusterConfig(
1976             EdsClusterConfig.newBuilder()
1977                 .setEdsConfig(
1978                     ConfigSource.newBuilder()
1979                         .setAds(AggregatedConfigSource.getDefaultInstance()))
1980                 .setServiceName("service-foo.googleapis.com"))
1981         .setLbPolicy(LbPolicy.LEAST_REQUEST)
1982         .build();
1983 
1984     CdsUpdate update = XdsClusterResource.processCluster(
1985         cluster, null, LRS_SERVER_INFO,
1986         LoadBalancerRegistry.getDefaultRegistry());
1987     LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig());
1988     assertThat(lbConfig.getPolicyName()).isEqualTo("wrr_locality_experimental");
1989     List<LbConfig> childConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(
1990         JsonUtil.getListOfObjects(lbConfig.getRawConfigValue(), "childPolicy"));
1991     assertThat(childConfigs.get(0).getPolicyName()).isEqualTo("least_request_experimental");
1992   }
1993 
1994   @Test
parseCluster_WrrLbPolicy_defaultLbConfig()1995   public void parseCluster_WrrLbPolicy_defaultLbConfig() throws ResourceInvalidException {
1996     LoadBalancingPolicy wrrConfig =
1997         LoadBalancingPolicy.newBuilder().addPolicies(
1998                 LoadBalancingPolicy.Policy.newBuilder()
1999                     .setTypedExtensionConfig(TypedExtensionConfig.newBuilder()
2000                         .setName("backend")
2001                         .setTypedConfig(
2002                             Any.pack(ClientSideWeightedRoundRobin.newBuilder()
2003                                 .setBlackoutPeriod(Duration.newBuilder().setSeconds(17).build())
2004                                 .setEnableOobLoadReport(
2005                                     BoolValue.newBuilder().setValue(true).build())
2006                                 .setErrorUtilizationPenalty(
2007                                     FloatValue.newBuilder().setValue(1.75F).build())
2008                                 .build()))
2009                         .build())
2010                     .build())
2011             .build();
2012 
2013     Cluster cluster = Cluster.newBuilder()
2014             .setName("cluster-foo.googleapis.com")
2015             .setType(DiscoveryType.EDS)
2016             .setEdsClusterConfig(
2017                 EdsClusterConfig.newBuilder()
2018                     .setEdsConfig(
2019                         ConfigSource.newBuilder()
2020                             .setAds(AggregatedConfigSource.getDefaultInstance()))
2021                             .setServiceName("service-foo.googleapis.com"))
2022             .setLoadBalancingPolicy(
2023                 LoadBalancingPolicy.newBuilder().addPolicies(
2024                     LoadBalancingPolicy.Policy.newBuilder()
2025                         .setTypedExtensionConfig(
2026                             TypedExtensionConfig.newBuilder()
2027                                 .setTypedConfig(
2028                                     Any.pack(WrrLocality.newBuilder()
2029                                         .setEndpointPickingPolicy(wrrConfig)
2030                                         .build()))
2031                                 .build())
2032                         .build())
2033                    .build())
2034               .build();
2035     CdsUpdate update = XdsClusterResource.processCluster(
2036             cluster, null, LRS_SERVER_INFO,
2037             LoadBalancerRegistry.getDefaultRegistry());
2038     LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig());
2039     assertThat(lbConfig.getPolicyName()).isEqualTo("wrr_locality_experimental");
2040     List<LbConfig> childConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(
2041             JsonUtil.getListOfObjects(lbConfig.getRawConfigValue(), "childPolicy"));
2042     assertThat(childConfigs.get(0).getPolicyName()).isEqualTo("weighted_round_robin");
2043     WeightedRoundRobinLoadBalancerConfig result = (WeightedRoundRobinLoadBalancerConfig)
2044         new WeightedRoundRobinLoadBalancerProvider().parseLoadBalancingPolicyConfig(
2045         childConfigs.get(0).getRawConfigValue()).getConfig();
2046     assertThat(result.blackoutPeriodNanos).isEqualTo(17_000_000_000L);
2047     assertThat(result.enableOobLoadReport).isTrue();
2048     assertThat(result.oobReportingPeriodNanos).isEqualTo(10_000_000_000L);
2049     assertThat(result.weightUpdatePeriodNanos).isEqualTo(1_000_000_000L);
2050     assertThat(result.weightExpirationPeriodNanos).isEqualTo(180_000_000_000L);
2051     assertThat(result.errorUtilizationPenalty).isEqualTo(1.75F);
2052   }
2053 
2054   @Test
parseCluster_transportSocketMatches_exception()2055   public void parseCluster_transportSocketMatches_exception() throws ResourceInvalidException {
2056     Cluster cluster = Cluster.newBuilder()
2057         .setName("cluster-foo.googleapis.com")
2058         .setType(DiscoveryType.EDS)
2059         .setEdsClusterConfig(
2060             EdsClusterConfig.newBuilder()
2061                 .setEdsConfig(
2062                     ConfigSource.newBuilder()
2063                         .setAds(AggregatedConfigSource.getDefaultInstance()))
2064                 .setServiceName("service-foo.googleapis.com"))
2065         .setLbPolicy(LbPolicy.ROUND_ROBIN)
2066         .addTransportSocketMatches(
2067             Cluster.TransportSocketMatch.newBuilder().setName("match1").build())
2068         .build();
2069 
2070     thrown.expect(ResourceInvalidException.class);
2071     thrown.expectMessage(
2072         "Cluster cluster-foo.googleapis.com: transport-socket-matches not supported.");
2073     XdsClusterResource.processCluster(cluster, null, LRS_SERVER_INFO,
2074         LoadBalancerRegistry.getDefaultRegistry());
2075   }
2076 
2077   @Test
parseCluster_validateEdsSourceConfig()2078   public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidException {
2079     Cluster cluster1 = Cluster.newBuilder()
2080         .setName("cluster-foo.googleapis.com")
2081         .setType(DiscoveryType.EDS)
2082         .setEdsClusterConfig(
2083             EdsClusterConfig.newBuilder()
2084                 .setEdsConfig(
2085                     ConfigSource.newBuilder()
2086                         .setAds(AggregatedConfigSource.getDefaultInstance()))
2087                 .setServiceName("service-foo.googleapis.com"))
2088         .setLbPolicy(LbPolicy.ROUND_ROBIN)
2089         .build();
2090     XdsClusterResource.processCluster(cluster1, null, LRS_SERVER_INFO,
2091         LoadBalancerRegistry.getDefaultRegistry());
2092 
2093     Cluster cluster2 = Cluster.newBuilder()
2094         .setName("cluster-foo.googleapis.com")
2095         .setType(DiscoveryType.EDS)
2096         .setEdsClusterConfig(
2097             EdsClusterConfig.newBuilder()
2098                 .setEdsConfig(
2099                     ConfigSource.newBuilder()
2100                         .setSelf(SelfConfigSource.getDefaultInstance()))
2101                 .setServiceName("service-foo.googleapis.com"))
2102         .setLbPolicy(LbPolicy.ROUND_ROBIN)
2103         .build();
2104     XdsClusterResource.processCluster(cluster2, null, LRS_SERVER_INFO,
2105         LoadBalancerRegistry.getDefaultRegistry());
2106 
2107     Cluster cluster3 = Cluster.newBuilder()
2108         .setName("cluster-foo.googleapis.com")
2109         .setType(DiscoveryType.EDS)
2110         .setEdsClusterConfig(
2111             EdsClusterConfig.newBuilder()
2112                 .setEdsConfig(
2113                     ConfigSource.newBuilder()
2114                         .setPathConfigSource(PathConfigSource.newBuilder().setPath("foo-path")))
2115                 .setServiceName("service-foo.googleapis.com"))
2116         .setLbPolicy(LbPolicy.ROUND_ROBIN)
2117         .build();
2118 
2119     thrown.expect(ResourceInvalidException.class);
2120     thrown.expectMessage(
2121         "Cluster cluster-foo.googleapis.com: field eds_cluster_config must be set to indicate to"
2122             + " use EDS over ADS or self ConfigSource");
2123     XdsClusterResource.processCluster(cluster3, null, LRS_SERVER_INFO,
2124         LoadBalancerRegistry.getDefaultRegistry());
2125   }
2126 
2127   @Test
parseServerSideListener_invalidTrafficDirection()2128   public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException {
2129     Listener listener =
2130         Listener.newBuilder()
2131             .setName("listener1")
2132             .setTrafficDirection(TrafficDirection.OUTBOUND)
2133             .build();
2134     thrown.expect(ResourceInvalidException.class);
2135     thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND");
2136     XdsListenerResource.parseServerSideListener(
2137         listener, null, filterRegistry, null);
2138   }
2139 
2140   @Test
parseServerSideListener_noTrafficDirection()2141   public void parseServerSideListener_noTrafficDirection() throws ResourceInvalidException {
2142     Listener listener =
2143         Listener.newBuilder()
2144             .setName("listener1")
2145             .build();
2146     XdsListenerResource.parseServerSideListener(
2147         listener, null, filterRegistry, null);
2148   }
2149 
2150   @Test
parseServerSideListener_listenerFiltersPresent()2151   public void parseServerSideListener_listenerFiltersPresent() throws ResourceInvalidException {
2152     Listener listener =
2153         Listener.newBuilder()
2154             .setName("listener1")
2155             .setTrafficDirection(TrafficDirection.INBOUND)
2156             .addListenerFilters(ListenerFilter.newBuilder().build())
2157             .build();
2158     thrown.expect(ResourceInvalidException.class);
2159     thrown.expectMessage("Listener listener1 cannot have listener_filters");
2160     XdsListenerResource.parseServerSideListener(
2161         listener, null, filterRegistry, null);
2162   }
2163 
2164   @Test
parseServerSideListener_useOriginalDst()2165   public void parseServerSideListener_useOriginalDst() throws ResourceInvalidException {
2166     Listener listener =
2167         Listener.newBuilder()
2168             .setName("listener1")
2169             .setTrafficDirection(TrafficDirection.INBOUND)
2170             .setUseOriginalDst(BoolValue.of(true))
2171             .build();
2172     thrown.expect(ResourceInvalidException.class);
2173     thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true");
2174     XdsListenerResource.parseServerSideListener(
2175         listener,null, filterRegistry, null);
2176   }
2177 
2178   @Test
parseServerSideListener_nonUniqueFilterChainMatch()2179   public void parseServerSideListener_nonUniqueFilterChainMatch() throws ResourceInvalidException {
2180     Filter filter1 = buildHttpConnectionManagerFilter(
2181         HttpFilter.newBuilder().setName("http-filter-1").setTypedConfig(
2182             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2183     FilterChainMatch filterChainMatch1 =
2184         FilterChainMatch.newBuilder()
2185             .addAllSourcePorts(Arrays.asList(80, 8080))
2186             .addAllPrefixRanges(Arrays.asList(CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2187                     .setPrefixLen(UInt32Value.of(16)).build(),
2188                 CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
2189                     .build()))
2190             .build();
2191     FilterChain filterChain1 =
2192         FilterChain.newBuilder()
2193             .setName("filter-chain-1")
2194             .setFilterChainMatch(filterChainMatch1)
2195             .addFilters(filter1)
2196             .build();
2197     Filter filter2 = buildHttpConnectionManagerFilter(
2198         HttpFilter.newBuilder().setName("http-filter-2").setTypedConfig(
2199             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2200     FilterChainMatch filterChainMatch2 =
2201         FilterChainMatch.newBuilder()
2202             .addAllSourcePorts(Arrays.asList(443, 8080))
2203             .addAllPrefixRanges(Arrays.asList(
2204                 CidrRange.newBuilder().setAddressPrefix("2001:DB8::8:800:200C:417A")
2205                     .setPrefixLen(UInt32Value.of(60)).build(),
2206                 CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2207                     .setPrefixLen(UInt32Value.of(16)).build()))
2208             .build();
2209     FilterChain filterChain2 =
2210         FilterChain.newBuilder()
2211             .setName("filter-chain-2")
2212             .setFilterChainMatch(filterChainMatch2)
2213             .addFilters(filter2)
2214             .build();
2215     Listener listener =
2216         Listener.newBuilder()
2217             .setName("listener1")
2218             .setTrafficDirection(TrafficDirection.INBOUND)
2219             .addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
2220             .build();
2221     thrown.expect(ResourceInvalidException.class);
2222     thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:");
2223     XdsListenerResource.parseServerSideListener(
2224         listener, null, filterRegistry, null);
2225   }
2226 
2227   @Test
parseServerSideListener_nonUniqueFilterChainMatch_sameFilter()2228   public void parseServerSideListener_nonUniqueFilterChainMatch_sameFilter()
2229       throws ResourceInvalidException {
2230     Filter filter1 = buildHttpConnectionManagerFilter(
2231         HttpFilter.newBuilder().setName("http-filter-1").setTypedConfig(
2232             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2233     FilterChainMatch filterChainMatch1 =
2234         FilterChainMatch.newBuilder()
2235             .addAllSourcePorts(Arrays.asList(80, 8080))
2236             .addAllPrefixRanges(Arrays.asList(
2237                 CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
2238                     .build()))
2239             .build();
2240     FilterChain filterChain1 =
2241         FilterChain.newBuilder()
2242             .setName("filter-chain-1")
2243             .setFilterChainMatch(filterChainMatch1)
2244             .addFilters(filter1)
2245             .build();
2246     Filter filter2 = buildHttpConnectionManagerFilter(
2247         HttpFilter.newBuilder().setName("http-filter-2").setTypedConfig(
2248             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2249     FilterChainMatch filterChainMatch2 =
2250         FilterChainMatch.newBuilder()
2251             .addAllSourcePorts(Arrays.asList(443, 8080))
2252             .addAllPrefixRanges(Arrays.asList(
2253                 CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2254                     .setPrefixLen(UInt32Value.of(16)).build(),
2255                 CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2256                     .setPrefixLen(UInt32Value.of(16)).build()))
2257             .build();
2258     FilterChain filterChain2 =
2259         FilterChain.newBuilder()
2260             .setName("filter-chain-2")
2261             .setFilterChainMatch(filterChainMatch2)
2262             .addFilters(filter2)
2263             .build();
2264     Listener listener =
2265         Listener.newBuilder()
2266             .setName("listener1")
2267             .setTrafficDirection(TrafficDirection.INBOUND)
2268             .addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
2269             .build();
2270     thrown.expect(ResourceInvalidException.class);
2271     thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:");
2272     XdsListenerResource.parseServerSideListener(
2273         listener,null, filterRegistry, null);
2274   }
2275 
2276   @Test
parseServerSideListener_uniqueFilterChainMatch()2277   public void parseServerSideListener_uniqueFilterChainMatch() throws ResourceInvalidException {
2278     Filter filter1 = buildHttpConnectionManagerFilter(
2279         HttpFilter.newBuilder().setName("http-filter-1").setTypedConfig(
2280             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2281     FilterChainMatch filterChainMatch1 =
2282         FilterChainMatch.newBuilder()
2283             .addAllSourcePorts(Arrays.asList(80, 8080))
2284             .addAllPrefixRanges(Arrays.asList(CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2285                     .setPrefixLen(UInt32Value.of(16)).build(),
2286                 CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
2287                     .build()))
2288             .setSourceType(FilterChainMatch.ConnectionSourceType.EXTERNAL)
2289             .build();
2290     FilterChain filterChain1 =
2291         FilterChain.newBuilder()
2292             .setName("filter-chain-1")
2293             .setFilterChainMatch(filterChainMatch1)
2294             .addFilters(filter1)
2295             .build();
2296     Filter filter2 = buildHttpConnectionManagerFilter(
2297         HttpFilter.newBuilder().setName("http-filter-2").setTypedConfig(
2298             Any.pack(Router.newBuilder().build())).setIsOptional(true).build());
2299     FilterChainMatch filterChainMatch2 =
2300         FilterChainMatch.newBuilder()
2301             .addAllSourcePorts(Arrays.asList(443, 8080))
2302             .addAllPrefixRanges(Arrays.asList(
2303                 CidrRange.newBuilder().setAddressPrefix("2001:DB8::8:800:200C:417A")
2304                     .setPrefixLen(UInt32Value.of(60)).build(),
2305                 CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
2306                     .setPrefixLen(UInt32Value.of(16)).build()))
2307             .setSourceType(FilterChainMatch.ConnectionSourceType.ANY)
2308             .build();
2309     FilterChain filterChain2 =
2310         FilterChain.newBuilder()
2311             .setName("filter-chain-2")
2312             .setFilterChainMatch(filterChainMatch2)
2313             .addFilters(filter2)
2314             .build();
2315     Listener listener =
2316         Listener.newBuilder()
2317             .setName("listener1")
2318             .setTrafficDirection(TrafficDirection.INBOUND)
2319             .addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
2320             .build();
2321     XdsListenerResource.parseServerSideListener(
2322         listener, null, filterRegistry, null);
2323   }
2324 
2325   @Test
parseFilterChain_noHcm()2326   public void parseFilterChain_noHcm() throws ResourceInvalidException {
2327     FilterChain filterChain =
2328         FilterChain.newBuilder()
2329             .setName("filter-chain-foo")
2330             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2331             .setTransportSocket(TransportSocket.getDefaultInstance())
2332             .build();
2333     thrown.expect(ResourceInvalidException.class);
2334     thrown.expectMessage(
2335         "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter");
2336     XdsListenerResource.parseFilterChain(
2337         filterChain, null, filterRegistry, null, null);
2338   }
2339 
2340   @Test
parseFilterChain_duplicateFilter()2341   public void parseFilterChain_duplicateFilter() throws ResourceInvalidException {
2342     Filter filter = buildHttpConnectionManagerFilter(
2343         HttpFilter.newBuilder().setName("http-filter-foo").setIsOptional(true).build());
2344     FilterChain filterChain =
2345         FilterChain.newBuilder()
2346             .setName("filter-chain-foo")
2347             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2348             .setTransportSocket(TransportSocket.getDefaultInstance())
2349             .addAllFilters(Arrays.asList(filter, filter))
2350             .build();
2351     thrown.expect(ResourceInvalidException.class);
2352     thrown.expectMessage(
2353         "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter");
2354     XdsListenerResource.parseFilterChain(
2355         filterChain, null, filterRegistry, null, null);
2356   }
2357 
2358   @Test
parseFilterChain_filterMissingTypedConfig()2359   public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidException {
2360     Filter filter = Filter.newBuilder().setName("envoy.http_connection_manager").build();
2361     FilterChain filterChain =
2362         FilterChain.newBuilder()
2363             .setName("filter-chain-foo")
2364             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2365             .setTransportSocket(TransportSocket.getDefaultInstance())
2366             .addFilters(filter)
2367             .build();
2368     thrown.expect(ResourceInvalidException.class);
2369     thrown.expectMessage(
2370         "FilterChain filter-chain-foo contains filter envoy.http_connection_manager "
2371             + "without typed_config");
2372     XdsListenerResource.parseFilterChain(
2373         filterChain, null, filterRegistry, null, null);
2374   }
2375 
2376   @Test
parseFilterChain_unsupportedFilter()2377   public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException {
2378     Filter filter =
2379         Filter.newBuilder()
2380             .setName("unsupported")
2381             .setTypedConfig(Any.newBuilder().setTypeUrl("unsupported-type-url"))
2382             .build();
2383     FilterChain filterChain =
2384         FilterChain.newBuilder()
2385             .setName("filter-chain-foo")
2386             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2387             .setTransportSocket(TransportSocket.getDefaultInstance())
2388             .addFilters(filter)
2389             .build();
2390     thrown.expect(ResourceInvalidException.class);
2391     thrown.expectMessage(
2392         "FilterChain filter-chain-foo contains filter unsupported with unsupported "
2393             + "typed_config type unsupported-type-url");
2394     XdsListenerResource.parseFilterChain(
2395         filterChain, null, filterRegistry, null, null);
2396   }
2397 
2398   @Test
parseFilterChain_noName()2399   public void parseFilterChain_noName() throws ResourceInvalidException {
2400     FilterChain filterChain1 =
2401         FilterChain.newBuilder()
2402             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2403             .addFilters(buildHttpConnectionManagerFilter(
2404                 HttpFilter.newBuilder()
2405                     .setName("http-filter-foo")
2406                     .setIsOptional(true)
2407                     .setTypedConfig(Any.pack(Router.newBuilder().build()))
2408                     .build()))
2409             .build();
2410     FilterChain filterChain2 =
2411         FilterChain.newBuilder()
2412             .setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2413             .addFilters(buildHttpConnectionManagerFilter(
2414                 HttpFilter.newBuilder()
2415                     .setName("http-filter-bar")
2416                     .setTypedConfig(Any.pack(Router.newBuilder().build()))
2417                     .setIsOptional(true)
2418                     .build()))
2419             .build();
2420 
2421     EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain(
2422         filterChain1, null, filterRegistry, null,
2423         null);
2424     EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain(
2425         filterChain2, null, filterRegistry, null,
2426         null);
2427     assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name());
2428   }
2429 
2430   @Test
validateCommonTlsContext_tlsParams()2431   public void validateCommonTlsContext_tlsParams() throws ResourceInvalidException {
2432     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2433             .setTlsParams(TlsParameters.getDefaultInstance())
2434             .build();
2435     thrown.expect(ResourceInvalidException.class);
2436     thrown.expectMessage("common-tls-context with tls_params is not supported");
2437     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2438   }
2439 
2440   @Test
validateCommonTlsContext_customHandshaker()2441   public void validateCommonTlsContext_customHandshaker() throws ResourceInvalidException {
2442     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2443             .setCustomHandshaker(TypedExtensionConfig.getDefaultInstance())
2444             .build();
2445     thrown.expect(ResourceInvalidException.class);
2446     thrown.expectMessage("common-tls-context with custom_handshaker is not supported");
2447     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2448   }
2449 
2450   @Test
validateCommonTlsContext_validationContext()2451   public void validateCommonTlsContext_validationContext() throws ResourceInvalidException {
2452     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2453             .setValidationContext(CertificateValidationContext.getDefaultInstance())
2454             .build();
2455     thrown.expect(ResourceInvalidException.class);
2456     thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
2457     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2458   }
2459 
2460   @Test
validateCommonTlsContext_validationContextSdsSecretConfig()2461   public void validateCommonTlsContext_validationContextSdsSecretConfig()
2462       throws ResourceInvalidException {
2463     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2464         .setValidationContextSdsSecretConfig(SdsSecretConfig.getDefaultInstance())
2465         .build();
2466     thrown.expect(ResourceInvalidException.class);
2467     thrown.expectMessage(
2468         "common-tls-context with validation_context_sds_secret_config is not supported");
2469     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2470   }
2471 
2472   @Test
2473   @SuppressWarnings("deprecation")
validateCommonTlsContext_validationContextCertificateProvider()2474   public void validateCommonTlsContext_validationContextCertificateProvider()
2475       throws ResourceInvalidException {
2476     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2477         .setValidationContextCertificateProvider(
2478             CommonTlsContext.CertificateProvider.getDefaultInstance())
2479         .build();
2480     thrown.expect(ResourceInvalidException.class);
2481     thrown.expectMessage(
2482         "common-tls-context with validation_context_certificate_provider is not supported");
2483     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2484   }
2485 
2486   @Test
2487   @SuppressWarnings("deprecation")
validateCommonTlsContext_validationContextCertificateProviderInstance()2488   public void validateCommonTlsContext_validationContextCertificateProviderInstance()
2489       throws ResourceInvalidException {
2490     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2491         .setValidationContextCertificateProviderInstance(
2492             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2493         .build();
2494     thrown.expect(ResourceInvalidException.class);
2495     thrown.expectMessage(
2496         "common-tls-context with validation_context_certificate_provider_instance is not "
2497             + "supported");
2498     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2499   }
2500 
2501   @Test
validateCommonTlsContext_tlsCertificateProviderInstance_isRequiredForServer()2502   public void validateCommonTlsContext_tlsCertificateProviderInstance_isRequiredForServer()
2503       throws ResourceInvalidException {
2504     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2505         .build();
2506     thrown.expect(ResourceInvalidException.class);
2507     thrown.expectMessage(
2508         "tls_certificate_provider_instance is required in downstream-tls-context");
2509     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, true);
2510   }
2511 
2512   @Test
2513   @SuppressWarnings("deprecation")
validateCommonTlsContext_tlsNewCertificateProviderInstance()2514   public void validateCommonTlsContext_tlsNewCertificateProviderInstance()
2515       throws ResourceInvalidException {
2516     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2517         .setTlsCertificateProviderInstance(
2518             CertificateProviderPluginInstance.newBuilder().setInstanceName("name1").build())
2519         .build();
2520     XdsClusterResource
2521         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
2522   }
2523 
2524   @Test
2525   @SuppressWarnings("deprecation")
validateCommonTlsContext_tlsCertificateProviderInstance()2526   public void validateCommonTlsContext_tlsCertificateProviderInstance()
2527       throws ResourceInvalidException {
2528     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2529         .setTlsCertificateCertificateProviderInstance(
2530             CertificateProviderInstance.newBuilder().setInstanceName("name1").build())
2531         .build();
2532     XdsClusterResource
2533         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
2534   }
2535 
2536   @Test
2537   @SuppressWarnings("deprecation")
validateCommonTlsContext_tlsCertificateProviderInstance_absentInBootstrapFile()2538   public void validateCommonTlsContext_tlsCertificateProviderInstance_absentInBootstrapFile()
2539           throws ResourceInvalidException {
2540     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2541         .setTlsCertificateCertificateProviderInstance(
2542             CertificateProviderInstance.newBuilder().setInstanceName("bad-name").build())
2543         .build();
2544     thrown.expect(ResourceInvalidException.class);
2545     thrown.expectMessage(
2546         "CertificateProvider instance name 'bad-name' not defined in the bootstrap file.");
2547     XdsClusterResource
2548         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
2549   }
2550 
2551   @Test
2552   @SuppressWarnings("deprecation")
validateCommonTlsContext_validationContextProviderInstance()2553   public void validateCommonTlsContext_validationContextProviderInstance()
2554           throws ResourceInvalidException {
2555     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2556         .setCombinedValidationContext(
2557             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2558                 .setValidationContextCertificateProviderInstance(
2559                     CertificateProviderInstance.newBuilder().setInstanceName("name1").build())
2560                 .build())
2561         .build();
2562     XdsClusterResource
2563         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
2564   }
2565 
2566   @Test
2567   @SuppressWarnings("deprecation")
validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile()2568   public void validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile()
2569           throws ResourceInvalidException {
2570     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2571         .setCombinedValidationContext(
2572             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2573                 .setValidationContextCertificateProviderInstance(
2574                     CertificateProviderInstance.newBuilder().setInstanceName("bad-name").build())
2575                 .build())
2576         .build();
2577     thrown.expect(ResourceInvalidException.class);
2578     thrown.expectMessage(
2579         "ca_certificate_provider_instance name 'bad-name' not defined in the bootstrap file.");
2580     XdsClusterResource
2581         .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
2582   }
2583 
2584 
2585   @Test
validateCommonTlsContext_tlsCertificatesCount()2586   public void validateCommonTlsContext_tlsCertificatesCount() throws ResourceInvalidException {
2587     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2588             .addTlsCertificates(TlsCertificate.getDefaultInstance())
2589             .build();
2590     thrown.expect(ResourceInvalidException.class);
2591     thrown.expectMessage("tls_certificate_provider_instance is unset");
2592     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2593   }
2594 
2595   @Test
validateCommonTlsContext_tlsCertificateSdsSecretConfigsCount()2596   public void validateCommonTlsContext_tlsCertificateSdsSecretConfigsCount()
2597       throws ResourceInvalidException {
2598     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2599         .addTlsCertificateSdsSecretConfigs(SdsSecretConfig.getDefaultInstance())
2600         .build();
2601     thrown.expect(ResourceInvalidException.class);
2602     thrown.expectMessage(
2603         "tls_certificate_provider_instance is unset");
2604     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2605   }
2606 
2607   @Test
2608   @SuppressWarnings("deprecation")
validateCommonTlsContext_tlsCertificateCertificateProvider()2609   public void validateCommonTlsContext_tlsCertificateCertificateProvider()
2610       throws ResourceInvalidException {
2611     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2612         .setTlsCertificateCertificateProvider(
2613             CommonTlsContext.CertificateProvider.getDefaultInstance())
2614         .build();
2615     thrown.expect(ResourceInvalidException.class);
2616     thrown.expectMessage(
2617         "tls_certificate_provider_instance is unset");
2618     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2619   }
2620 
2621   @Test
validateCommonTlsContext_combinedValidationContext_isRequiredForClient()2622   public void validateCommonTlsContext_combinedValidationContext_isRequiredForClient()
2623       throws ResourceInvalidException {
2624     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2625         .build();
2626     thrown.expect(ResourceInvalidException.class);
2627     thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
2628     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2629   }
2630 
2631   @Test
validateCommonTlsContext_combinedValidationContextWithoutCertProviderInstance()2632   public void validateCommonTlsContext_combinedValidationContextWithoutCertProviderInstance()
2633       throws ResourceInvalidException {
2634     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2635         .setCombinedValidationContext(
2636             CommonTlsContext.CombinedCertificateValidationContext.getDefaultInstance())
2637         .build();
2638     thrown.expect(ResourceInvalidException.class);
2639     thrown.expectMessage(
2640         "ca_certificate_provider_instance is required in upstream-tls-context");
2641     XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
2642   }
2643 
2644   @Test
2645   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValContextWithDefaultValContextForServer()2646   public void validateCommonTlsContext_combinedValContextWithDefaultValContextForServer()
2647       throws ResourceInvalidException, InvalidProtocolBufferException {
2648     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2649         .setCombinedValidationContext(
2650             CombinedCertificateValidationContext.newBuilder()
2651                 .setValidationContextCertificateProviderInstance(
2652                     CertificateProviderInstance.getDefaultInstance())
2653                 .setDefaultValidationContext(CertificateValidationContext.newBuilder()
2654                     .addMatchSubjectAltNames(StringMatcher.newBuilder().setExact("foo.com").build())
2655                     .build()))
2656         .setTlsCertificateCertificateProviderInstance(
2657             CertificateProviderInstance.getDefaultInstance())
2658         .build();
2659     thrown.expect(ResourceInvalidException.class);
2660     thrown.expectMessage("match_subject_alt_names only allowed in upstream_tls_context");
2661     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true);
2662   }
2663 
2664   @Test
2665   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertSpki()2666   public void validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertSpki()
2667       throws ResourceInvalidException {
2668     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2669         .setCombinedValidationContext(
2670             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2671                 .setValidationContextCertificateProviderInstance(
2672                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2673                 .setDefaultValidationContext(
2674                     CertificateValidationContext.newBuilder().addVerifyCertificateSpki("foo")))
2675         .setTlsCertificateCertificateProviderInstance(
2676             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2677         .build();
2678     thrown.expect(ResourceInvalidException.class);
2679     thrown.expectMessage("verify_certificate_spki in default_validation_context is not "
2680         + "supported");
2681     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
2682   }
2683 
2684   @Test
2685   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertHash()2686   public void validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertHash()
2687       throws ResourceInvalidException {
2688     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2689         .setCombinedValidationContext(
2690             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2691                 .setValidationContextCertificateProviderInstance(
2692                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2693                 .setDefaultValidationContext(
2694                     CertificateValidationContext.newBuilder().addVerifyCertificateHash("foo")))
2695         .setTlsCertificateCertificateProviderInstance(
2696             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2697         .build();
2698     thrown.expect(ResourceInvalidException.class);
2699     thrown.expectMessage("verify_certificate_hash in default_validation_context is not "
2700         + "supported");
2701     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
2702   }
2703 
2704   @Test
2705   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValContextDfltValContextRequireSignedCertTimestamp()2706   public void validateCommonTlsContext_combinedValContextDfltValContextRequireSignedCertTimestamp()
2707       throws ResourceInvalidException {
2708     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2709         .setCombinedValidationContext(
2710             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2711                 .setValidationContextCertificateProviderInstance(
2712                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2713                 .setDefaultValidationContext(CertificateValidationContext.newBuilder()
2714                     .setRequireSignedCertificateTimestamp(BoolValue.of(true))))
2715         .setTlsCertificateCertificateProviderInstance(
2716             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2717         .build();
2718     thrown.expect(ResourceInvalidException.class);
2719     thrown.expectMessage(
2720         "require_signed_certificate_timestamp in default_validation_context is not "
2721             + "supported");
2722     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
2723   }
2724 
2725   @Test
2726   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValidationContextWithDefaultValidationContextCrl()2727   public void validateCommonTlsContext_combinedValidationContextWithDefaultValidationContextCrl()
2728       throws ResourceInvalidException {
2729     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2730         .setCombinedValidationContext(
2731             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2732                 .setValidationContextCertificateProviderInstance(
2733                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2734                 .setDefaultValidationContext(CertificateValidationContext.newBuilder()
2735                     .setCrl(DataSource.getDefaultInstance())))
2736         .setTlsCertificateCertificateProviderInstance(
2737             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2738         .build();
2739     thrown.expect(ResourceInvalidException.class);
2740     thrown.expectMessage("crl in default_validation_context is not supported");
2741     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
2742   }
2743 
2744   @Test
2745   @SuppressWarnings("deprecation")
validateCommonTlsContext_combinedValContextWithDfltValContextCustomValidatorConfig()2746   public void validateCommonTlsContext_combinedValContextWithDfltValContextCustomValidatorConfig()
2747       throws ResourceInvalidException {
2748     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2749         .setCombinedValidationContext(
2750             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2751                 .setValidationContextCertificateProviderInstance(
2752                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2753                 .setDefaultValidationContext(CertificateValidationContext.newBuilder()
2754                     .setCustomValidatorConfig(TypedExtensionConfig.getDefaultInstance())))
2755         .setTlsCertificateCertificateProviderInstance(
2756             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2757         .build();
2758     thrown.expect(ResourceInvalidException.class);
2759     thrown.expectMessage("custom_validator_config in default_validation_context is not "
2760         + "supported");
2761     XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
2762   }
2763 
2764   @Test
validateDownstreamTlsContext_noCommonTlsContext()2765   public void validateDownstreamTlsContext_noCommonTlsContext() throws ResourceInvalidException {
2766     DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.getDefaultInstance();
2767     thrown.expect(ResourceInvalidException.class);
2768     thrown.expectMessage("common-tls-context is required in downstream-tls-context");
2769     XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, null);
2770   }
2771 
2772   @Test
2773   @SuppressWarnings("deprecation")
validateDownstreamTlsContext_hasRequireSni()2774   public void validateDownstreamTlsContext_hasRequireSni() throws ResourceInvalidException {
2775     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2776         .setCombinedValidationContext(
2777             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2778                 .setValidationContextCertificateProviderInstance(
2779                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance()))
2780         .setTlsCertificateCertificateProviderInstance(
2781             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2782         .build();
2783     DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.newBuilder()
2784         .setCommonTlsContext(commonTlsContext)
2785         .setRequireSni(BoolValue.of(true))
2786         .build();
2787     thrown.expect(ResourceInvalidException.class);
2788     thrown.expectMessage("downstream-tls-context with require-sni is not supported");
2789     XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of(""));
2790   }
2791 
2792   @Test
2793   @SuppressWarnings("deprecation")
validateDownstreamTlsContext_hasOcspStaplePolicy()2794   public void validateDownstreamTlsContext_hasOcspStaplePolicy() throws ResourceInvalidException {
2795     CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
2796         .setCombinedValidationContext(
2797             CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
2798                 .setValidationContextCertificateProviderInstance(
2799                     CommonTlsContext.CertificateProviderInstance.getDefaultInstance()))
2800         .setTlsCertificateCertificateProviderInstance(
2801             CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
2802         .build();
2803     DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.newBuilder()
2804         .setCommonTlsContext(commonTlsContext)
2805         .setOcspStaplePolicy(DownstreamTlsContext.OcspStaplePolicy.STRICT_STAPLING)
2806         .build();
2807     thrown.expect(ResourceInvalidException.class);
2808     thrown.expectMessage(
2809         "downstream-tls-context with ocsp_staple_policy value STRICT_STAPLING is not supported");
2810     XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of(""));
2811   }
2812 
2813   @Test
validateUpstreamTlsContext_noCommonTlsContext()2814   public void validateUpstreamTlsContext_noCommonTlsContext() throws ResourceInvalidException {
2815     UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext.getDefaultInstance();
2816     thrown.expect(ResourceInvalidException.class);
2817     thrown.expectMessage("common-tls-context is required in upstream-tls-context");
2818     XdsClusterResource.validateUpstreamTlsContext(upstreamTlsContext, null);
2819   }
2820 
2821   @Test
validateResourceName()2822   public void validateResourceName() {
2823     String traditionalResource = "cluster1.google.com";
2824     assertThat(XdsClient.isResourceNameValid(traditionalResource,
2825         XdsClusterResource.getInstance().typeUrl()))
2826         .isTrue();
2827 
2828     String invalidPath = "xdstp:/abc/efg";
2829     assertThat(XdsClient.isResourceNameValid(invalidPath,
2830         XdsClusterResource.getInstance().typeUrl())).isFalse();
2831 
2832     String invalidPath2 = "xdstp:///envoy.config.route.v3.RouteConfiguration";
2833     assertThat(XdsClient.isResourceNameValid(invalidPath2,
2834         XdsRouteConfigureResource.getInstance().typeUrl())).isFalse();
2835 
2836     String typeMatch = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1";
2837     assertThat(XdsClient.isResourceNameValid(typeMatch,
2838         XdsListenerResource.getInstance().typeUrl())).isFalse();
2839     assertThat(XdsClient.isResourceNameValid(typeMatch,
2840         XdsRouteConfigureResource.getInstance().typeUrl())).isTrue();
2841   }
2842 
2843   @Test
canonifyResourceName()2844   public void canonifyResourceName() {
2845     String traditionalResource = "cluster1.google.com";
2846     assertThat(XdsClient.canonifyResourceName(traditionalResource))
2847         .isEqualTo(traditionalResource);
2848     assertThat(XdsClient.canonifyResourceName(traditionalResource))
2849         .isEqualTo(traditionalResource);
2850     assertThat(XdsClient.canonifyResourceName(traditionalResource))
2851         .isEqualTo(traditionalResource);
2852     assertThat(XdsClient.canonifyResourceName(traditionalResource))
2853         .isEqualTo(traditionalResource);
2854 
2855     String withNoQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1";
2856     assertThat(XdsClient.canonifyResourceName(withNoQueries)).isEqualTo(withNoQueries);
2857 
2858     String withOneQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1?name=foo";
2859     assertThat(XdsClient.canonifyResourceName(withOneQueries)).isEqualTo(withOneQueries);
2860 
2861     String withTwoQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/id/route1?b=1&a=1";
2862     String expectedCanonifiedName =
2863         "xdstp:///envoy.config.route.v3.RouteConfiguration/id/route1?a=1&b=1";
2864     assertThat(XdsClient.canonifyResourceName(withTwoQueries))
2865         .isEqualTo(expectedCanonifiedName);
2866   }
2867 
2868   /**
2869    *  Tests compliance with RFC 3986 section 3.3
2870    *  https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
2871    */
2872   @Test
percentEncodePath()2873   public void percentEncodePath()  {
2874     String unreserved = "aAzZ09-._~";
2875     assertThat(XdsClient.percentEncodePath(unreserved)).isEqualTo(unreserved);
2876 
2877     String subDelims = "!$&'(*+,;/=";
2878     assertThat(XdsClient.percentEncodePath(subDelims)).isEqualTo(subDelims);
2879 
2880     String colonAndAt = ":@";
2881     assertThat(XdsClient.percentEncodePath(colonAndAt)).isEqualTo(colonAndAt);
2882 
2883     String needBeEncoded = "?#[]";
2884     assertThat(XdsClient.percentEncodePath(needBeEncoded)).isEqualTo("%3F%23%5B%5D");
2885 
2886     String ipv4 = "0.0.0.0:8080";
2887     assertThat(XdsClient.percentEncodePath(ipv4)).isEqualTo(ipv4);
2888 
2889     String ipv6 = "[::1]:8080";
2890     assertThat(XdsClient.percentEncodePath(ipv6)).isEqualTo("%5B::1%5D:8080");
2891   }
2892 
buildHttpConnectionManagerFilter(HttpFilter... httpFilters)2893   private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters) {
2894     return Filter.newBuilder()
2895         .setName("envoy.http_connection_manager")
2896         .setTypedConfig(
2897             Any.pack(
2898                 HttpConnectionManager.newBuilder()
2899                     .setRds(
2900                         Rds.newBuilder()
2901                             .setRouteConfigName("route-config.googleapis.com")
2902                             .setConfigSource(
2903                                 ConfigSource.newBuilder()
2904                                     .setAds(AggregatedConfigSource.getDefaultInstance())))
2905                     .addAllHttpFilters(Arrays.asList(httpFilters))
2906                     .build(),
2907                 "type.googleapis.com"))
2908         .build();
2909   }
2910 }
2911