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