Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xds: xdsNameResolver match channel overrideAuthority in virtualHost matching #9405

Merged
merged 3 commits into from Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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