diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index 78b35a14e38..62ec4d167a4 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -261,6 +261,7 @@ public static final class Args { @Nullable private final ScheduledExecutorService scheduledExecutorService; @Nullable private final ChannelLogger channelLogger; @Nullable private final Executor executor; + @Nullable private final String overrideAuthority; private Args( Integer defaultPort, @@ -269,7 +270,8 @@ private Args( ServiceConfigParser serviceConfigParser, @Nullable ScheduledExecutorService scheduledExecutorService, @Nullable ChannelLogger channelLogger, - @Nullable Executor executor) { + @Nullable Executor executor, + @Nullable String overrideAuthority) { this.defaultPort = checkNotNull(defaultPort, "defaultPort not set"); this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set"); this.syncContext = checkNotNull(syncContext, "syncContext not set"); @@ -277,6 +279,7 @@ private Args( this.scheduledExecutorService = scheduledExecutorService; this.channelLogger = channelLogger; this.executor = executor; + this.overrideAuthority = overrideAuthority; } /** @@ -362,6 +365,20 @@ public Executor getOffloadExecutor() { return executor; } + /** + * Returns the overrideAuthority from channel {@link ManagedChannelBuilder#overrideAuthority}. + * Overrides the host name for L7 HTTP virtual host matching. Almost all name resolvers should + * not use this. + * + * @since 1.49.0 + */ + @Nullable + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406") + public String getOverrideAuthority() { + return overrideAuthority; + } + + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -372,6 +389,7 @@ public String toString() { .add("scheduledExecutorService", scheduledExecutorService) .add("channelLogger", channelLogger) .add("executor", executor) + .add("overrideAuthority", overrideAuthority) .toString(); } @@ -389,6 +407,7 @@ public Builder toBuilder() { builder.setScheduledExecutorService(scheduledExecutorService); builder.setChannelLogger(channelLogger); builder.setOffloadExecutor(executor); + builder.setOverrideAuthority(overrideAuthority); return builder; } @@ -414,6 +433,7 @@ public static final class Builder { private ScheduledExecutorService scheduledExecutorService; private ChannelLogger channelLogger; private Executor executor; + private String overrideAuthority; Builder() { } @@ -490,6 +510,17 @@ public Builder setOffloadExecutor(Executor executor) { return this; } + /** + * See {@link Args#getOverrideAuthority()}. This is an optional field. + * + * @since 1.49.0 + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9406") + public Builder setOverrideAuthority(String authority) { + this.overrideAuthority = authority; + return this; + } + /** * Builds an {@link Args}. * @@ -499,7 +530,7 @@ public Args build() { return new Args( defaultPort, proxyDetector, syncContext, serviceConfigParser, - scheduledExecutorService, channelLogger, executor); + scheduledExecutorService, channelLogger, executor, overrideAuthority); } } } diff --git a/api/src/test/java/io/grpc/NameResolverTest.java b/api/src/test/java/io/grpc/NameResolverTest.java index 0b5b198713b..f825de354af 100644 --- a/api/src/test/java/io/grpc/NameResolverTest.java +++ b/api/src/test/java/io/grpc/NameResolverTest.java @@ -40,6 +40,7 @@ public class NameResolverTest { mock(ScheduledExecutorService.class); private final ChannelLogger channelLogger = mock(ChannelLogger.class); private final Executor executor = Executors.newSingleThreadExecutor(); + private final String overrideAuthority = "grpc.io"; @Test public void args() { @@ -51,6 +52,7 @@ public void args() { assertThat(args.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService); assertThat(args.getChannelLogger()).isSameInstanceAs(channelLogger); assertThat(args.getOffloadExecutor()).isSameInstanceAs(executor); + assertThat(args.getOverrideAuthority()).isSameInstanceAs(overrideAuthority); NameResolver.Args args2 = args.toBuilder().build(); assertThat(args2.getDefaultPort()).isEqualTo(defaultPort); @@ -60,6 +62,7 @@ public void args() { assertThat(args2.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService); assertThat(args2.getChannelLogger()).isSameInstanceAs(channelLogger); assertThat(args2.getOffloadExecutor()).isSameInstanceAs(executor); + assertThat(args2.getOverrideAuthority()).isSameInstanceAs(overrideAuthority); assertThat(args2).isNotSameInstanceAs(args); assertThat(args2).isNotEqualTo(args); @@ -74,6 +77,7 @@ private NameResolver.Args createArgs() { .setScheduledExecutorService(scheduledExecutorService) .setChannelLogger(channelLogger) .setOffloadExecutor(executor) + .setOverrideAuthority(overrideAuthority) .build(); } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 00e9fb81a69..3e2e3ec17fc 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -645,6 +645,7 @@ ClientStream newSubstream( builder.maxRetryAttempts, builder.maxHedgedAttempts, loadBalancerFactory); + this.authorityOverride = builder.authorityOverride; this.nameResolverArgs = NameResolver.Args.newBuilder() .setDefaultPort(builder.getDefaultPort()) @@ -654,8 +655,8 @@ ClientStream newSubstream( .setServiceConfigParser(serviceConfigParser) .setChannelLogger(channelLogger) .setOffloadExecutor(this.offloadExecutorHolder) + .setOverrideAuthority(this.authorityOverride) .build(); - this.authorityOverride = builder.authorityOverride; this.nameResolverFactory = builder.nameResolverFactory; this.nameResolver = getNameResolver( target, authorityOverride, nameResolverFactory, nameResolverArgs); diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 5019d8eea93..1fe2d720b96 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -111,6 +111,7 @@ final class XdsNameResolver extends NameResolver { @Nullable private final String targetAuthority; private final String serviceAuthority; + private final String overrideAuthority; private final ServiceConfigParser serviceConfigParser; private final SynchronizationContext syncContext; private final ScheduledExecutorService scheduler; @@ -134,22 +135,25 @@ final class XdsNameResolver extends NameResolver { private boolean receivedConfig; XdsNameResolver( - @Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser, + @Nullable String targetAuthority, String name, @Nullable String overrideAuthority, + ServiceConfigParser serviceConfigParser, SynchronizationContext syncContext, ScheduledExecutorService scheduler, @Nullable Map bootstrapOverride) { - this(targetAuthority, name, serviceConfigParser, syncContext, scheduler, + this(targetAuthority, name, overrideAuthority, serviceConfigParser, syncContext, scheduler, SharedXdsClientPoolProvider.getDefaultProvider(), ThreadSafeRandomImpl.instance, FilterRegistry.getDefaultRegistry(), bootstrapOverride); } @VisibleForTesting XdsNameResolver( - @Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser, + @Nullable String targetAuthority, String name, @Nullable String overrideAuthority, + ServiceConfigParser serviceConfigParser, SynchronizationContext syncContext, ScheduledExecutorService scheduler, XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random, FilterRegistry filterRegistry, @Nullable Map bootstrapOverride) { this.targetAuthority = targetAuthority; serviceAuthority = GrpcUtil.checkAuthority(checkNotNull(name, "name")); + this.overrideAuthority = overrideAuthority; this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.scheduler = checkNotNull(scheduler, "scheduler"); @@ -769,9 +773,10 @@ private void stop() { // called in syncContext private void updateRoutes(List virtualHosts, long httpMaxStreamDurationNano, @Nullable List filterConfigs) { - VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, ldsResourceName); + String authority = overrideAuthority != null ? overrideAuthority : ldsResourceName; + VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, authority); if (virtualHost == null) { - String error = "Failed to find virtual host matching hostname: " + ldsResourceName; + String error = "Failed to find virtual host matching hostname: " + authority; logger.log(XdsLogLevel.WARNING, error); cleanUpRoutes(error); return; diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java index 0eb51c91281..4875a85ea63 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java @@ -79,8 +79,9 @@ public XdsNameResolver newNameResolver(URI targetUri, Args args) { targetUri); String name = targetPath.substring(1); return new XdsNameResolver( - targetUri.getAuthority(), name, args.getServiceConfigParser(), - args.getSynchronizationContext(), args.getScheduledExecutorService(), + targetUri.getAuthority(), name, args.getOverrideAuthority(), + args.getServiceConfigParser(), args.getSynchronizationContext(), + args.getScheduledExecutorService(), bootstrapOverride); } return null; diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 76ca00eacc6..25bd866424d 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -167,7 +167,8 @@ public void setUp() { FilterRegistry filterRegistry = FilterRegistry.newRegistry().register( new FaultFilter(mockRandom, new AtomicLong()), RouterFilter.INSTANCE); - resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler, + resolver = new XdsNameResolver(null, AUTHORITY, null, + serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, filterRegistry, null); } @@ -200,7 +201,8 @@ public ObjectPool getOrCreate() throws XdsInitializationException { throw new XdsInitializationException("Fail to read bootstrap file"); } }; - resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler, + resolver = new XdsNameResolver(null, AUTHORITY, null, + serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener).onError(errorCaptor.capture()); @@ -213,7 +215,7 @@ public ObjectPool getOrCreate() throws XdsInitializationException { @Test public void resolving_withTargetAuthorityNotFound() { resolver = new XdsNameResolver( - "notfound.google.com", AUTHORITY, serviceConfigParser, syncContext, scheduler, + "notfound.google.com", AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener).onError(errorCaptor.capture()); @@ -234,7 +236,8 @@ public void resolving_noTargetAuthority_templateWithoutXdstp() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; expectedLdsResourceName = "[::FFFF:129.144.52.38]:80/id=1"; resolver = new XdsNameResolver( - null, serviceAuthority, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, + null, serviceAuthority, null, serviceConfigParser, syncContext, + scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener, never()).onError(any(Status.class)); @@ -254,7 +257,7 @@ public void resolving_noTargetAuthority_templateWithXdstp() { "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?id=1"; resolver = new XdsNameResolver( - null, serviceAuthority, serviceConfigParser, syncContext, scheduler, + null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener, never()).onError(any(Status.class)); @@ -277,7 +280,7 @@ public void resolving_targetAuthorityInAuthoritiesMap() { expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified resolver = new XdsNameResolver( - "xds.authority.com", serviceAuthority, serviceConfigParser, syncContext, scheduler, + "xds.authority.com", serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener, never()).onError(any(Status.class)); @@ -465,6 +468,61 @@ public void resolving_encounterErrorLdsAndRdsWatchers() { + ". xDS server returned: UNAVAILABLE: server unreachable"); } + @SuppressWarnings("unchecked") + @Test + public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { + Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), + RouteAction.forCluster( + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); + VirtualHost virtualHost = + VirtualHost.create("virtualhost", Collections.singletonList("random"), + Collections.singletonList(route), + ImmutableMap.of()); + + resolver = new XdsNameResolver(null, AUTHORITY, "random", + serviceConfigParser, syncContext, scheduler, + xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate(0L, Arrays.asList(virtualHost)); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + assertServiceConfigForLoadBalancingConfig( + Collections.singletonList(cluster1), + (Map) resolutionResultCaptor.getValue().getServiceConfig().getConfig()); + } + + @Test + public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() { + Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), + RouteAction.forCluster( + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); + VirtualHost virtualHost = + VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), + Collections.singletonList(route), + ImmutableMap.of()); + + resolver = new XdsNameResolver(null, AUTHORITY, "random", + serviceConfigParser, syncContext, scheduler, + xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate(0L, Arrays.asList(virtualHost)); + assertEmptyResolutionResult("random"); + } + + @Test + public void resolving_matchingVirtualHostNotFoundForOverrideAuthority() { + resolver = new XdsNameResolver(null, AUTHORITY, AUTHORITY, + serviceConfigParser, syncContext, scheduler, + xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate(0L, buildUnmatchedVirtualHosts()); + assertEmptyResolutionResult(expectedLdsResourceName); + } + @Test public void resolving_matchingVirtualHostNotFoundInLdsResource() { resolver.start(mockListener); @@ -541,7 +599,7 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { ServiceConfigParser realParser = new ScParser( true, 5, 5, new AutoConfiguredLoadBalancerFactory("pick-first")); - resolver = new XdsNameResolver(null, AUTHORITY, realParser, syncContext, scheduler, + resolver = new XdsNameResolver(null, AUTHORITY, null, realParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); @@ -744,7 +802,8 @@ public void resolved_rpcHashingByChannelId() { // A different resolver/Channel. resolver.shutdown(); reset(mockListener); - resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler, + resolver = new XdsNameResolver(null, AUTHORITY, null, serviceConfigParser, + syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); xdsClient = (FakeXdsClient) resolver.getXdsClient();