diff --git a/xds/src/main/java/io/grpc/xds/RoutingUtils.java b/xds/src/main/java/io/grpc/xds/RoutingUtils.java index 8bf879f43b..4bdb72a41f 100644 --- a/xds/src/main/java/io/grpc/xds/RoutingUtils.java +++ b/xds/src/main/java/io/grpc/xds/RoutingUtils.java @@ -149,7 +149,7 @@ final class RoutingUtils { return false; } for (HeaderMatcher headerMatcher : routeMatch.headerMatchers()) { - if (!matchHeader(headerMatcher, getHeaderValue(headers, headerMatcher.name()))) { + if (!headerMatcher.matches(getHeaderValue(headers, headerMatcher.name()))) { return false; } } @@ -170,35 +170,6 @@ final class RoutingUtils { return pathMatcher.regEx().matches(fullMethodName); } - private static boolean matchHeader(HeaderMatcher headerMatcher, @Nullable String value) { - if (headerMatcher.present() != null) { - return (value == null) == headerMatcher.present().equals(headerMatcher.inverted()); - } - if (value == null) { - return false; - } - boolean baseMatch; - if (headerMatcher.exactValue() != null) { - baseMatch = headerMatcher.exactValue().equals(value); - } else if (headerMatcher.safeRegEx() != null) { - baseMatch = headerMatcher.safeRegEx().matches(value); - } else if (headerMatcher.range() != null) { - long numValue; - try { - numValue = Long.parseLong(value); - baseMatch = numValue >= headerMatcher.range().start() - && numValue <= headerMatcher.range().end(); - } catch (NumberFormatException ignored) { - baseMatch = false; - } - } else if (headerMatcher.prefix() != null) { - baseMatch = value.startsWith(headerMatcher.prefix()); - } else { - baseMatch = value.endsWith(headerMatcher.suffix()); - } - return baseMatch != headerMatcher.inverted(); - } - @Nullable private static String getHeaderValue(Metadata headers, String headerName) { if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 8a5992ab61..7f853dcf1e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -60,16 +60,12 @@ import io.grpc.xds.VirtualHost.Route.RouteAction; import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight; import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; -import io.grpc.xds.VirtualHost.Route.RouteMatch; -import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; import io.grpc.xds.XdsClient.ResourceWatcher; import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; -import io.grpc.xds.internal.Matchers.FractionMatcher; -import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -304,47 +300,6 @@ final class XdsNameResolver extends NameResolver { receivedConfig = true; } - @VisibleForTesting - @Nullable - static VirtualHost findVirtualHostForHostName(List virtualHosts, String hostName) { - // Domain search order: - // 1. Exact domain names: ``www.foo.com``. - // 2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``. - // 3. Prefix domain wildcards: ``foo.*`` or ``foo-*``. - // 4. Special wildcard ``*`` matching any domain. - // - // The longest wildcards match first. - // Assuming only a single virtual host in the entire route configuration can match - // on ``*`` and a domain must be unique across all virtual hosts. - int matchingLen = -1; // longest length of wildcard pattern that matches host name - boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found - VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain - for (VirtualHost vHost : virtualHosts) { - for (String domain : vHost.domains()) { - boolean selected = false; - if (matchHostName(hostName, domain)) { // matching - if (!domain.contains("*")) { // exact matching - exactMatchFound = true; - targetVirtualHost = vHost; - break; - } else if (domain.length() > matchingLen) { // longer matching pattern - selected = true; - } else if (domain.length() == matchingLen && domain.startsWith("*")) { // suffix matching - selected = true; - } - } - if (selected) { - matchingLen = domain.length(); - targetVirtualHost = vHost; - } - } - if (exactMatchFound) { - break; - } - } - return targetVirtualHost; - } - /** * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with * case-insensitive. @@ -418,7 +373,8 @@ final class XdsNameResolver extends NameResolver { routingCfg = routingConfig; selectedOverrideConfigs = new HashMap<>(routingCfg.virtualHostOverrideConfig); for (Route route : routingCfg.routes) { - if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(), + if (RoutingUtils.matchRoute( + route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(), headers, random)) { selectedRoute = route; selectedOverrideConfigs.putAll(route.filterConfigOverrides()); @@ -619,34 +575,6 @@ final class XdsNameResolver extends NameResolver { }; } - @VisibleForTesting - static boolean matchRoute(RouteMatch routeMatch, String fullMethodName, - Metadata headers, ThreadSafeRandom random) { - if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) { - return false; - } - for (HeaderMatcher headerMatcher : routeMatch.headerMatchers()) { - if (!headerMatcher.matches(getHeaderValue(headers, headerMatcher.name()))) { - return false; - } - } - FractionMatcher fraction = routeMatch.fractionMatcher(); - return fraction == null || random.nextInt(fraction.denominator()) < fraction.numerator(); - } - - private static boolean matchPath(PathMatcher pathMatcher, String fullMethodName) { - if (pathMatcher.path() != null) { - return pathMatcher.caseSensitive() - ? pathMatcher.path().equals(fullMethodName) - : pathMatcher.path().equalsIgnoreCase(fullMethodName); - } else if (pathMatcher.prefix() != null) { - return pathMatcher.caseSensitive() - ? fullMethodName.startsWith(pathMatcher.prefix()) - : fullMethodName.toLowerCase().startsWith(pathMatcher.prefix().toLowerCase()); - } - return pathMatcher.regEx().matches(fullMethodName); - } - @Nullable private static String getHeaderValue(Metadata headers, String headerName) { if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { @@ -775,7 +703,7 @@ final class XdsNameResolver extends NameResolver { private void updateRoutes(List virtualHosts, long httpMaxStreamDurationNano, @Nullable List filterConfigs) { String authority = overrideAuthority != null ? overrideAuthority : ldsResourceName; - VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, authority); + VirtualHost virtualHost = RoutingUtils.findVirtualHostForHostName(virtualHosts, authority); if (virtualHost == null) { String error = "Failed to find virtual host matching hostname: " + authority; logger.log(XdsLogLevel.WARNING, error); diff --git a/xds/src/test/java/io/grpc/xds/RoutingUtilsTest.java b/xds/src/test/java/io/grpc/xds/RoutingUtilsTest.java new file mode 100644 index 0000000000..a460501e85 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/RoutingUtilsTest.java @@ -0,0 +1,231 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import com.google.common.collect.ImmutableMap; +import com.google.re2j.Pattern; +import io.grpc.Metadata; +import io.grpc.xds.VirtualHost.Route; +import io.grpc.xds.VirtualHost.Route.RouteMatch; +import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.internal.Matchers.HeaderMatcher; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RoutingUtils }. */ +@RunWith(JUnit4.class) +public class RoutingUtilsTest { + + @Test + public void findVirtualHostForHostName_exactMatchFirst() { + String hostname = "a.googleapis.com"; + List routes = Collections.emptyList(); + VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", + Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, + ImmutableMap.of()); + VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", + Collections.singletonList("*.googleapis.com"), routes, + ImmutableMap.of()); + VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", + Collections.singletonList("*"), routes, + ImmutableMap.of()); + List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); + assertThat(RoutingUtils.findVirtualHostForHostName(virtualHosts, hostname)) + .isEqualTo(vHost1); + } + + @Test + public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() { + String hostname = "a.googleapis.com"; + List routes = Collections.emptyList(); + VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", + Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, + ImmutableMap.of()); + VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", + Collections.singletonList("a.googleapis.*"), routes, + ImmutableMap.of()); + VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", + Collections.singletonList("*"), routes, + ImmutableMap.of()); + List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); + assertThat(RoutingUtils.findVirtualHostForHostName(virtualHosts, hostname)) + .isEqualTo(vHost1); + } + + @Test + public void findVirtualHostForHostName_asteriskMatchAnyDomain() { + String hostname = "a.googleapis.com"; + List routes = Collections.emptyList(); + VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", + Collections.singletonList("*"), routes, + ImmutableMap.of()); + VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", + Collections.singletonList("b.googleapis.com"), routes, + ImmutableMap.of()); + List virtualHosts = Arrays.asList(vHost1, vHost2); + assertThat(RoutingUtils.findVirtualHostForHostName(virtualHosts, hostname)) + .isEqualTo(vHost1); + } + + @Test + public void routeMatching_pathOnly() { + Metadata headers = new Metadata(); + ThreadSafeRandom random = mock(ThreadSafeRandom.class); + + RouteMatch routeMatch1 = + RouteMatch.create( + PathMatcher.fromPath("/FooService/barMethod", true), + Collections.emptyList(), null); + assertThat(RoutingUtils.matchRoute(routeMatch1, "/FooService/barMethod", headers, random)) + .isTrue(); + assertThat(RoutingUtils.matchRoute(routeMatch1, "/FooService/bazMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch2 = + RouteMatch.create( + PathMatcher.fromPrefix("/FooService/", true), + Collections.emptyList(), null); + assertThat(RoutingUtils.matchRoute(routeMatch2, "/FooService/barMethod", headers, random)) + .isTrue(); + assertThat(RoutingUtils.matchRoute(routeMatch2, "/FooService/bazMethod", headers, random)) + .isTrue(); + assertThat(RoutingUtils.matchRoute(routeMatch2, "/BarService/bazMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch3 = + RouteMatch.create( + PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")), + Collections.emptyList(), null); + assertThat(RoutingUtils.matchRoute(routeMatch3, "/FooService/barMethod", headers, random)) + .isTrue(); + } + + @Test + public void routeMatching_pathOnly_caseInsensitive() { + Metadata headers = new Metadata(); + ThreadSafeRandom random = mock(ThreadSafeRandom.class); + + RouteMatch routeMatch1 = + RouteMatch.create( + PathMatcher.fromPath("/FooService/barMethod", false), + Collections.emptyList(), null); + assertThat(RoutingUtils.matchRoute(routeMatch1, "/fooservice/barmethod", headers, random)) + .isTrue(); + + RouteMatch routeMatch2 = + RouteMatch.create( + PathMatcher.fromPrefix("/FooService", false), + Collections.emptyList(), null); + assertThat(RoutingUtils.matchRoute(routeMatch2, "/fooservice/barmethod", headers, random)) + .isTrue(); + } + + @Test + public void routeMatching_withHeaders() { + Metadata headers = new Metadata(); + headers.put(Metadata.Key.of("authority", Metadata.ASCII_STRING_MARSHALLER), + "foo.googleapis.com"); + headers.put(Metadata.Key.of("grpc-encoding", Metadata.ASCII_STRING_MARSHALLER), "gzip"); + headers.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "gRPC-Java"); + headers.put(Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER), "1000"); + headers.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "custom-value1"); + headers.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "custom-value2"); + ThreadSafeRandom random = mock(ThreadSafeRandom.class); + + PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true); + RouteMatch routeMatch1 = RouteMatch.create( + pathMatcher, + Arrays.asList( + HeaderMatcher.forExactValue("grpc-encoding", "gzip", false), + HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), false), + HeaderMatcher.forRange( + "content-length", HeaderMatcher.Range.create(100, 10000), false), + HeaderMatcher.forPresent("user-agent", true, false), + HeaderMatcher.forPrefix("custom-key", "custom-", false), + HeaderMatcher.forSuffix("custom-key", "value2", false)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch1, "/FooService/barMethod", headers, random)) + .isTrue(); + + RouteMatch routeMatch2 = RouteMatch.create( + pathMatcher, + Collections.singletonList( + HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), true)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch2, "/FooService/barMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch3 = RouteMatch.create( + pathMatcher, + Collections.singletonList( + HeaderMatcher.forExactValue("user-agent", "gRPC-Go", false)), null); + assertThat(RoutingUtils.matchRoute(routeMatch3, "/FooService/barMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch4 = RouteMatch.create( + pathMatcher, + Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, false)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch4, "/FooService/barMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch5 = RouteMatch.create( + pathMatcher, + Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, true)), // inverted + null); + assertThat(RoutingUtils.matchRoute(routeMatch5, "/FooService/barMethod", headers, random)) + .isTrue(); + + RouteMatch routeMatch6 = RouteMatch.create( + pathMatcher, + Collections.singletonList(HeaderMatcher.forPresent("user-agent", true, true)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch6, "/FooService/barMethod", headers, random)) + .isFalse(); + + RouteMatch routeMatch7 = RouteMatch.create( + pathMatcher, + Collections.singletonList( + HeaderMatcher.forExactValue("custom-key", "custom-value1,custom-value2", false)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch7, "/FooService/barMethod", headers, random)) + .isTrue(); + + RouteMatch routeMatch8 = RouteMatch.create( + pathMatcher, + Collections.singletonList( + HeaderMatcher.forExactValue("content-type", "application/grpc", false)), + null); + assertThat(RoutingUtils.matchRoute( + routeMatch8, "/FooService/barMethod", new Metadata(), random)).isTrue(); + + RouteMatch routeMatch9 = RouteMatch.create( + pathMatcher, + Collections.singletonList( + HeaderMatcher.forExactValue("custom-key!", "custom-value1,custom-value2", false)), + null); + assertThat(RoutingUtils.matchRoute(routeMatch9, "/FooService/barMethod", headers, random)) + .isFalse(); + } +} diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 496148a513..4b858c5716 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -90,7 +90,6 @@ import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; -import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -1450,57 +1449,6 @@ public class XdsNameResolverTest { assertThat(XdsNameResolver.matchHostName("foo-bar", pattern)).isTrue(); } - @Test - public void findVirtualHostForHostName_exactMatchFirst() { - String hostname = "a.googleapis.com"; - List routes = Collections.emptyList(); - VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", - Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, - ImmutableMap.of()); - VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", - Collections.singletonList("*.googleapis.com"), routes, - ImmutableMap.of()); - VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", - Collections.singletonList("*"), routes, - ImmutableMap.of()); - List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); - assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) - .isEqualTo(vHost1); - } - - @Test - public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() { - String hostname = "a.googleapis.com"; - List routes = Collections.emptyList(); - VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", - Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, - ImmutableMap.of()); - VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", - Collections.singletonList("a.googleapis.*"), routes, - ImmutableMap.of()); - VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", - Collections.singletonList("*"), routes, - ImmutableMap.of()); - List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); - assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) - .isEqualTo(vHost1); - } - - @Test - public void findVirtualHostForHostName_asteriskMatchAnyDomain() { - String hostname = "a.googleapis.com"; - List routes = Collections.emptyList(); - VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", - Collections.singletonList("*"), routes, - ImmutableMap.of()); - VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", - Collections.singletonList("b.googleapis.com"), routes, - ImmutableMap.of()); - List virtualHosts = Arrays.asList(vHost1, vHost2); - assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) - .isEqualTo(vHost1); - } - @Test public void resolved_faultAbortInLdsUpdate() { resolver.start(mockListener); @@ -1901,147 +1849,6 @@ public class XdsNameResolverTest { verifyRpcFailed(listener, expectedStatus); } - @Test - public void routeMatching_pathOnly() { - Metadata headers = new Metadata(); - ThreadSafeRandom random = mock(ThreadSafeRandom.class); - - RouteMatch routeMatch1 = - RouteMatch.create( - PathMatcher.fromPath("/FooService/barMethod", true), - Collections.emptyList(), null); - assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random)) - .isTrue(); - assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/bazMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch2 = - RouteMatch.create( - PathMatcher.fromPrefix("/FooService/", true), - Collections.emptyList(), null); - assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random)) - .isTrue(); - assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/bazMethod", headers, random)) - .isTrue(); - assertThat(XdsNameResolver.matchRoute(routeMatch2, "/BarService/bazMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch3 = - RouteMatch.create( - PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")), - Collections.emptyList(), null); - assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random)) - .isTrue(); - } - - @Test - public void routeMatching_pathOnly_caseInsensitive() { - Metadata headers = new Metadata(); - ThreadSafeRandom random = mock(ThreadSafeRandom.class); - - RouteMatch routeMatch1 = - RouteMatch.create( - PathMatcher.fromPath("/FooService/barMethod", false), - Collections.emptyList(), null); - assertThat(XdsNameResolver.matchRoute(routeMatch1, "/fooservice/barmethod", headers, random)) - .isTrue(); - - RouteMatch routeMatch2 = - RouteMatch.create( - PathMatcher.fromPrefix("/FooService", false), - Collections.emptyList(), null); - assertThat(XdsNameResolver.matchRoute(routeMatch2, "/fooservice/barmethod", headers, random)) - .isTrue(); - } - - @Test - public void routeMatching_withHeaders() { - Metadata headers = new Metadata(); - headers.put(Metadata.Key.of("authority", Metadata.ASCII_STRING_MARSHALLER), - "foo.googleapis.com"); - headers.put(Metadata.Key.of("grpc-encoding", Metadata.ASCII_STRING_MARSHALLER), "gzip"); - headers.put(Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), "gRPC-Java"); - headers.put(Metadata.Key.of("content-length", Metadata.ASCII_STRING_MARSHALLER), "1000"); - headers.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "custom-value1"); - headers.put(Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER), "custom-value2"); - ThreadSafeRandom random = mock(ThreadSafeRandom.class); - - PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true); - RouteMatch routeMatch1 = RouteMatch.create( - pathMatcher, - Arrays.asList( - HeaderMatcher.forExactValue("grpc-encoding", "gzip", false), - HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), false), - HeaderMatcher.forRange( - "content-length", HeaderMatcher.Range.create(100, 10000), false), - HeaderMatcher.forPresent("user-agent", true, false), - HeaderMatcher.forPrefix("custom-key", "custom-", false), - HeaderMatcher.forSuffix("custom-key", "value2", false)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random)) - .isTrue(); - - RouteMatch routeMatch2 = RouteMatch.create( - pathMatcher, - Collections.singletonList( - HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), true)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch3 = RouteMatch.create( - pathMatcher, - Collections.singletonList( - HeaderMatcher.forExactValue("user-agent", "gRPC-Go", false)), null); - assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch4 = RouteMatch.create( - pathMatcher, - Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, false)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch4, "/FooService/barMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch5 = RouteMatch.create( - pathMatcher, - Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, true)), // inverted - null); - assertThat(XdsNameResolver.matchRoute(routeMatch5, "/FooService/barMethod", headers, random)) - .isTrue(); - - RouteMatch routeMatch6 = RouteMatch.create( - pathMatcher, - Collections.singletonList(HeaderMatcher.forPresent("user-agent", true, true)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch6, "/FooService/barMethod", headers, random)) - .isFalse(); - - RouteMatch routeMatch7 = RouteMatch.create( - pathMatcher, - Collections.singletonList( - HeaderMatcher.forExactValue("custom-key", "custom-value1,custom-value2", false)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch7, "/FooService/barMethod", headers, random)) - .isTrue(); - - RouteMatch routeMatch8 = RouteMatch.create( - pathMatcher, - Collections.singletonList( - HeaderMatcher.forExactValue("content-type", "application/grpc", false)), - null); - assertThat(XdsNameResolver.matchRoute( - routeMatch8, "/FooService/barMethod", new Metadata(), random)).isTrue(); - - RouteMatch routeMatch9 = RouteMatch.create( - pathMatcher, - Collections.singletonList( - HeaderMatcher.forExactValue("custom-key!", "custom-value1,custom-value2", false)), - null); - assertThat(XdsNameResolver.matchRoute(routeMatch9, "/FooService/barMethod", headers, random)) - .isFalse(); - } - private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { @Override public void setBootstrapOverride(Map bootstrap) {}