Skip to content

Commit

Permalink
xds: xdsNameResolver match channel overrideAuthority in virtualHost …
Browse files Browse the repository at this point in the history
…matching (#9405)
  • Loading branch information
YifeiZhuang committed Jul 22, 2022
1 parent 58cd6e1 commit 027d36e
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 18 deletions.
35 changes: 33 additions & 2 deletions api/src/main/java/io/grpc/NameResolver.java
Expand Up @@ -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,
Expand All @@ -269,14 +270,16 @@ 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");
this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser not set");
this.scheduledExecutorService = scheduledExecutorService;
this.channelLogger = channelLogger;
this.executor = executor;
this.overrideAuthority = overrideAuthority;
}

/**
Expand Down Expand Up @@ -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)
Expand All @@ -372,6 +389,7 @@ public String toString() {
.add("scheduledExecutorService", scheduledExecutorService)
.add("channelLogger", channelLogger)
.add("executor", executor)
.add("overrideAuthority", overrideAuthority)
.toString();
}

Expand All @@ -389,6 +407,7 @@ public Builder toBuilder() {
builder.setScheduledExecutorService(scheduledExecutorService);
builder.setChannelLogger(channelLogger);
builder.setOffloadExecutor(executor);
builder.setOverrideAuthority(overrideAuthority);
return builder;
}

Expand All @@ -414,6 +433,7 @@ public static final class Builder {
private ScheduledExecutorService scheduledExecutorService;
private ChannelLogger channelLogger;
private Executor executor;
private String overrideAuthority;

Builder() {
}
Expand Down Expand Up @@ -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}.
*
Expand All @@ -499,7 +530,7 @@ public Args build() {
return
new Args(
defaultPort, proxyDetector, syncContext, serviceConfigParser,
scheduledExecutorService, channelLogger, executor);
scheduledExecutorService, channelLogger, executor, overrideAuthority);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions api/src/test/java/io/grpc/NameResolverTest.java
Expand Up @@ -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() {
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -74,6 +77,7 @@ private NameResolver.Args createArgs() {
.setScheduledExecutorService(scheduledExecutorService)
.setChannelLogger(channelLogger)
.setOffloadExecutor(executor)
.setOverrideAuthority(overrideAuthority)
.build();
}
}
3 changes: 2 additions & 1 deletion core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
Expand Up @@ -645,6 +645,7 @@ ClientStream newSubstream(
builder.maxRetryAttempts,
builder.maxHedgedAttempts,
loadBalancerFactory);
this.authorityOverride = builder.authorityOverride;
this.nameResolverArgs =
NameResolver.Args.newBuilder()
.setDefaultPort(builder.getDefaultPort())
Expand All @@ -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);
Expand Down
15 changes: 10 additions & 5 deletions xds/src/main/java/io/grpc/xds/XdsNameResolver.java
Expand Up @@ -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;
Expand All @@ -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<String, ?> 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<String, ?> 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");
Expand Down Expand Up @@ -769,9 +773,10 @@ private void stop() {
// called in syncContext
private void updateRoutes(List<VirtualHost> virtualHosts, long httpMaxStreamDurationNano,
@Nullable List<NamedFilterConfig> 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;
Expand Down
5 changes: 3 additions & 2 deletions xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java
Expand Up @@ -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;
Expand Down
75 changes: 67 additions & 8 deletions xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java
Expand Up @@ -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);
}

Expand Down Expand Up @@ -200,7 +201,8 @@ public ObjectPool<XdsClient> 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());
Expand All @@ -213,7 +215,7 @@ public ObjectPool<XdsClient> 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());
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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.<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
public void resolving_matchingVirtualHostNotFoundInLdsResource() {
resolver.start(mockListener);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 027d36e

Please sign in to comment.