xds: xdsNameResolver match channel overrideAuthority in virtualHost matching (#9405)

This commit is contained in:
yifeizhuang 2022-07-22 12:41:16 -07:00 committed by GitHub
parent 58cd6e1a7f
commit 027d36eee7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 18 deletions

View File

@ -261,6 +261,7 @@ public abstract class NameResolver {
@Nullable private final ScheduledExecutorService scheduledExecutorService; @Nullable private final ScheduledExecutorService scheduledExecutorService;
@Nullable private final ChannelLogger channelLogger; @Nullable private final ChannelLogger channelLogger;
@Nullable private final Executor executor; @Nullable private final Executor executor;
@Nullable private final String overrideAuthority;
private Args( private Args(
Integer defaultPort, Integer defaultPort,
@ -269,7 +270,8 @@ public abstract class NameResolver {
ServiceConfigParser serviceConfigParser, ServiceConfigParser serviceConfigParser,
@Nullable ScheduledExecutorService scheduledExecutorService, @Nullable ScheduledExecutorService scheduledExecutorService,
@Nullable ChannelLogger channelLogger, @Nullable ChannelLogger channelLogger,
@Nullable Executor executor) { @Nullable Executor executor,
@Nullable String overrideAuthority) {
this.defaultPort = checkNotNull(defaultPort, "defaultPort not set"); this.defaultPort = checkNotNull(defaultPort, "defaultPort not set");
this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set"); this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set");
this.syncContext = checkNotNull(syncContext, "syncContext not set"); this.syncContext = checkNotNull(syncContext, "syncContext not set");
@ -277,6 +279,7 @@ public abstract class NameResolver {
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
this.channelLogger = channelLogger; this.channelLogger = channelLogger;
this.executor = executor; this.executor = executor;
this.overrideAuthority = overrideAuthority;
} }
/** /**
@ -362,6 +365,20 @@ public abstract class NameResolver {
return executor; 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 @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -372,6 +389,7 @@ public abstract class NameResolver {
.add("scheduledExecutorService", scheduledExecutorService) .add("scheduledExecutorService", scheduledExecutorService)
.add("channelLogger", channelLogger) .add("channelLogger", channelLogger)
.add("executor", executor) .add("executor", executor)
.add("overrideAuthority", overrideAuthority)
.toString(); .toString();
} }
@ -389,6 +407,7 @@ public abstract class NameResolver {
builder.setScheduledExecutorService(scheduledExecutorService); builder.setScheduledExecutorService(scheduledExecutorService);
builder.setChannelLogger(channelLogger); builder.setChannelLogger(channelLogger);
builder.setOffloadExecutor(executor); builder.setOffloadExecutor(executor);
builder.setOverrideAuthority(overrideAuthority);
return builder; return builder;
} }
@ -414,6 +433,7 @@ public abstract class NameResolver {
private ScheduledExecutorService scheduledExecutorService; private ScheduledExecutorService scheduledExecutorService;
private ChannelLogger channelLogger; private ChannelLogger channelLogger;
private Executor executor; private Executor executor;
private String overrideAuthority;
Builder() { Builder() {
} }
@ -490,6 +510,17 @@ public abstract class NameResolver {
return this; 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}. * Builds an {@link Args}.
* *
@ -499,7 +530,7 @@ public abstract class NameResolver {
return return
new Args( new Args(
defaultPort, proxyDetector, syncContext, serviceConfigParser, defaultPort, proxyDetector, syncContext, serviceConfigParser,
scheduledExecutorService, channelLogger, executor); scheduledExecutorService, channelLogger, executor, overrideAuthority);
} }
} }
} }

View File

@ -40,6 +40,7 @@ public class NameResolverTest {
mock(ScheduledExecutorService.class); mock(ScheduledExecutorService.class);
private final ChannelLogger channelLogger = mock(ChannelLogger.class); private final ChannelLogger channelLogger = mock(ChannelLogger.class);
private final Executor executor = Executors.newSingleThreadExecutor(); private final Executor executor = Executors.newSingleThreadExecutor();
private final String overrideAuthority = "grpc.io";
@Test @Test
public void args() { public void args() {
@ -51,6 +52,7 @@ public class NameResolverTest {
assertThat(args.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService); assertThat(args.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService);
assertThat(args.getChannelLogger()).isSameInstanceAs(channelLogger); assertThat(args.getChannelLogger()).isSameInstanceAs(channelLogger);
assertThat(args.getOffloadExecutor()).isSameInstanceAs(executor); assertThat(args.getOffloadExecutor()).isSameInstanceAs(executor);
assertThat(args.getOverrideAuthority()).isSameInstanceAs(overrideAuthority);
NameResolver.Args args2 = args.toBuilder().build(); NameResolver.Args args2 = args.toBuilder().build();
assertThat(args2.getDefaultPort()).isEqualTo(defaultPort); assertThat(args2.getDefaultPort()).isEqualTo(defaultPort);
@ -60,6 +62,7 @@ public class NameResolverTest {
assertThat(args2.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService); assertThat(args2.getScheduledExecutorService()).isSameInstanceAs(scheduledExecutorService);
assertThat(args2.getChannelLogger()).isSameInstanceAs(channelLogger); assertThat(args2.getChannelLogger()).isSameInstanceAs(channelLogger);
assertThat(args2.getOffloadExecutor()).isSameInstanceAs(executor); assertThat(args2.getOffloadExecutor()).isSameInstanceAs(executor);
assertThat(args2.getOverrideAuthority()).isSameInstanceAs(overrideAuthority);
assertThat(args2).isNotSameInstanceAs(args); assertThat(args2).isNotSameInstanceAs(args);
assertThat(args2).isNotEqualTo(args); assertThat(args2).isNotEqualTo(args);
@ -74,6 +77,7 @@ public class NameResolverTest {
.setScheduledExecutorService(scheduledExecutorService) .setScheduledExecutorService(scheduledExecutorService)
.setChannelLogger(channelLogger) .setChannelLogger(channelLogger)
.setOffloadExecutor(executor) .setOffloadExecutor(executor)
.setOverrideAuthority(overrideAuthority)
.build(); .build();
} }
} }

View File

@ -645,6 +645,7 @@ final class ManagedChannelImpl extends ManagedChannel implements
builder.maxRetryAttempts, builder.maxRetryAttempts,
builder.maxHedgedAttempts, builder.maxHedgedAttempts,
loadBalancerFactory); loadBalancerFactory);
this.authorityOverride = builder.authorityOverride;
this.nameResolverArgs = this.nameResolverArgs =
NameResolver.Args.newBuilder() NameResolver.Args.newBuilder()
.setDefaultPort(builder.getDefaultPort()) .setDefaultPort(builder.getDefaultPort())
@ -654,8 +655,8 @@ final class ManagedChannelImpl extends ManagedChannel implements
.setServiceConfigParser(serviceConfigParser) .setServiceConfigParser(serviceConfigParser)
.setChannelLogger(channelLogger) .setChannelLogger(channelLogger)
.setOffloadExecutor(this.offloadExecutorHolder) .setOffloadExecutor(this.offloadExecutorHolder)
.setOverrideAuthority(this.authorityOverride)
.build(); .build();
this.authorityOverride = builder.authorityOverride;
this.nameResolverFactory = builder.nameResolverFactory; this.nameResolverFactory = builder.nameResolverFactory;
this.nameResolver = getNameResolver( this.nameResolver = getNameResolver(
target, authorityOverride, nameResolverFactory, nameResolverArgs); target, authorityOverride, nameResolverFactory, nameResolverArgs);

View File

@ -111,6 +111,7 @@ final class XdsNameResolver extends NameResolver {
@Nullable @Nullable
private final String targetAuthority; private final String targetAuthority;
private final String serviceAuthority; private final String serviceAuthority;
private final String overrideAuthority;
private final ServiceConfigParser serviceConfigParser; private final ServiceConfigParser serviceConfigParser;
private final SynchronizationContext syncContext; private final SynchronizationContext syncContext;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
@ -134,22 +135,25 @@ final class XdsNameResolver extends NameResolver {
private boolean receivedConfig; private boolean receivedConfig;
XdsNameResolver( XdsNameResolver(
@Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser, @Nullable String targetAuthority, String name, @Nullable String overrideAuthority,
ServiceConfigParser serviceConfigParser,
SynchronizationContext syncContext, ScheduledExecutorService scheduler, SynchronizationContext syncContext, ScheduledExecutorService scheduler,
@Nullable Map<String, ?> bootstrapOverride) { @Nullable Map<String, ?> bootstrapOverride) {
this(targetAuthority, name, serviceConfigParser, syncContext, scheduler, this(targetAuthority, name, overrideAuthority, serviceConfigParser, syncContext, scheduler,
SharedXdsClientPoolProvider.getDefaultProvider(), ThreadSafeRandomImpl.instance, SharedXdsClientPoolProvider.getDefaultProvider(), ThreadSafeRandomImpl.instance,
FilterRegistry.getDefaultRegistry(), bootstrapOverride); FilterRegistry.getDefaultRegistry(), bootstrapOverride);
} }
@VisibleForTesting @VisibleForTesting
XdsNameResolver( XdsNameResolver(
@Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser, @Nullable String targetAuthority, String name, @Nullable String overrideAuthority,
ServiceConfigParser serviceConfigParser,
SynchronizationContext syncContext, ScheduledExecutorService scheduler, SynchronizationContext syncContext, ScheduledExecutorService scheduler,
XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random, XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random,
FilterRegistry filterRegistry, @Nullable Map<String, ?> bootstrapOverride) { FilterRegistry filterRegistry, @Nullable Map<String, ?> bootstrapOverride) {
this.targetAuthority = targetAuthority; this.targetAuthority = targetAuthority;
serviceAuthority = GrpcUtil.checkAuthority(checkNotNull(name, "name")); serviceAuthority = GrpcUtil.checkAuthority(checkNotNull(name, "name"));
this.overrideAuthority = overrideAuthority;
this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser"); this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser");
this.syncContext = checkNotNull(syncContext, "syncContext"); this.syncContext = checkNotNull(syncContext, "syncContext");
this.scheduler = checkNotNull(scheduler, "scheduler"); this.scheduler = checkNotNull(scheduler, "scheduler");
@ -769,9 +773,10 @@ final class XdsNameResolver extends NameResolver {
// called in syncContext // called in syncContext
private void updateRoutes(List<VirtualHost> virtualHosts, long httpMaxStreamDurationNano, private void updateRoutes(List<VirtualHost> virtualHosts, long httpMaxStreamDurationNano,
@Nullable List<NamedFilterConfig> filterConfigs) { @Nullable List<NamedFilterConfig> filterConfigs) {
VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, ldsResourceName); String authority = overrideAuthority != null ? overrideAuthority : ldsResourceName;
VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, authority);
if (virtualHost == null) { 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); logger.log(XdsLogLevel.WARNING, error);
cleanUpRoutes(error); cleanUpRoutes(error);
return; return;

View File

@ -79,8 +79,9 @@ public final class XdsNameResolverProvider extends NameResolverProvider {
targetUri); targetUri);
String name = targetPath.substring(1); String name = targetPath.substring(1);
return new XdsNameResolver( return new XdsNameResolver(
targetUri.getAuthority(), name, args.getServiceConfigParser(), targetUri.getAuthority(), name, args.getOverrideAuthority(),
args.getSynchronizationContext(), args.getScheduledExecutorService(), args.getServiceConfigParser(), args.getSynchronizationContext(),
args.getScheduledExecutorService(),
bootstrapOverride); bootstrapOverride);
} }
return null; return null;

View File

@ -167,7 +167,8 @@ public class XdsNameResolverTest {
FilterRegistry filterRegistry = FilterRegistry.newRegistry().register( FilterRegistry filterRegistry = FilterRegistry.newRegistry().register(
new FaultFilter(mockRandom, new AtomicLong()), new FaultFilter(mockRandom, new AtomicLong()),
RouterFilter.INSTANCE); RouterFilter.INSTANCE);
resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler, resolver = new XdsNameResolver(null, AUTHORITY, null,
serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, filterRegistry, null); xdsClientPoolFactory, mockRandom, filterRegistry, null);
} }
@ -200,7 +201,8 @@ public class XdsNameResolverTest {
throw new XdsInitializationException("Fail to read bootstrap file"); 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); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
verify(mockListener).onError(errorCaptor.capture()); verify(mockListener).onError(errorCaptor.capture());
@ -213,7 +215,7 @@ public class XdsNameResolverTest {
@Test @Test
public void resolving_withTargetAuthorityNotFound() { public void resolving_withTargetAuthorityNotFound() {
resolver = new XdsNameResolver( resolver = new XdsNameResolver(
"notfound.google.com", AUTHORITY, serviceConfigParser, syncContext, scheduler, "notfound.google.com", AUTHORITY, null, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
verify(mockListener).onError(errorCaptor.capture()); verify(mockListener).onError(errorCaptor.capture());
@ -234,7 +236,8 @@ public class XdsNameResolverTest {
String serviceAuthority = "[::FFFF:129.144.52.38]:80"; String serviceAuthority = "[::FFFF:129.144.52.38]:80";
expectedLdsResourceName = "[::FFFF:129.144.52.38]:80/id=1"; expectedLdsResourceName = "[::FFFF:129.144.52.38]:80/id=1";
resolver = new XdsNameResolver( resolver = new XdsNameResolver(
null, serviceAuthority, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, null, serviceAuthority, null, serviceConfigParser, syncContext,
scheduler, xdsClientPoolFactory,
mockRandom, FilterRegistry.getDefaultRegistry(), null); mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
verify(mockListener, never()).onError(any(Status.class)); verify(mockListener, never()).onError(any(Status.class));
@ -254,7 +257,7 @@ public class XdsNameResolverTest {
"xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/"
+ "%5B::FFFF:129.144.52.38%5D:80?id=1"; + "%5B::FFFF:129.144.52.38%5D:80?id=1";
resolver = new XdsNameResolver( resolver = new XdsNameResolver(
null, serviceAuthority, serviceConfigParser, syncContext, scheduler, null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
verify(mockListener, never()).onError(any(Status.class)); verify(mockListener, never()).onError(any(Status.class));
@ -277,7 +280,7 @@ public class XdsNameResolverTest {
expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" 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 + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified
resolver = new XdsNameResolver( resolver = new XdsNameResolver(
"xds.authority.com", serviceAuthority, serviceConfigParser, syncContext, scheduler, "xds.authority.com", serviceAuthority, null, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
verify(mockListener, never()).onError(any(Status.class)); verify(mockListener, never()).onError(any(Status.class));
@ -465,6 +468,61 @@ public class XdsNameResolverTest {
+ ". xDS server returned: UNAVAILABLE: server unreachable"); + ". 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.<HashPolicy>emptyList(), TimeUnit.SECONDS.toNanos(15L), null),
ImmutableMap.<String, FilterConfig>of());
VirtualHost virtualHost =
VirtualHost.create("virtualhost", Collections.singletonList("random"),
Collections.singletonList(route),
ImmutableMap.<String, FilterConfig>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<String, ?>) resolutionResultCaptor.getValue().getServiceConfig().getConfig());
}
@Test
public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() {
Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
RouteAction.forCluster(
cluster1, Collections.<HashPolicy>emptyList(), TimeUnit.SECONDS.toNanos(15L), null),
ImmutableMap.<String, FilterConfig>of());
VirtualHost virtualHost =
VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY),
Collections.singletonList(route),
ImmutableMap.<String, FilterConfig>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 @Test
public void resolving_matchingVirtualHostNotFoundInLdsResource() { public void resolving_matchingVirtualHostNotFoundInLdsResource() {
resolver.start(mockListener); resolver.start(mockListener);
@ -541,7 +599,7 @@ public class XdsNameResolverTest {
public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() {
ServiceConfigParser realParser = new ScParser( ServiceConfigParser realParser = new ScParser(
true, 5, 5, new AutoConfiguredLoadBalancerFactory("pick-first")); 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); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
@ -744,7 +802,8 @@ public class XdsNameResolverTest {
// A different resolver/Channel. // A different resolver/Channel.
resolver.shutdown(); resolver.shutdown();
reset(mockListener); reset(mockListener);
resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler, resolver = new XdsNameResolver(null, AUTHORITY, null, serviceConfigParser,
syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener); resolver.start(mockListener);
xdsClient = (FakeXdsClient) resolver.getXdsClient(); xdsClient = (FakeXdsClient) resolver.getXdsClient();