From b31d6830a2af3196e588217320d2781112b7cf73 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Fri, 18 Sep 2020 17:55:29 -0700 Subject: [PATCH] xds: fail to create xDS channel if no server with supported channel creds found (#7400) Create the xDS channel outside the XdsClient. Throw an XdsInitializationException if the provided server list (parsed from the bootstrap file) can not be used to create such a channel. The exception is caught by the xDS resolver and propagated to the Channel gracefully as a name resolution error. --- .../java/io/grpc/xds/EdsLoadBalancer.java | 17 +-- .../java/io/grpc/xds/XdsChannelFactory.java | 100 ++++++++++++++++++ xds/src/main/java/io/grpc/xds/XdsClient.java | 68 ------------ .../main/java/io/grpc/xds/XdsClientImpl.java | 9 +- .../xds/XdsClientWrapperForServerSds.java | 9 +- .../java/io/grpc/xds/XdsNameResolver.java | 15 ++- .../io/grpc/xds/XdsNameResolverProvider.java | 24 ++--- .../java/io/grpc/xds/EdsLoadBalancerTest.java | 4 +- .../io/grpc/xds/XdsChannelFactoryTest.java | 99 +++++++++++++++++ .../java/io/grpc/xds/XdsClientImplTest.java | 18 +--- .../xds/XdsClientImplTestForListener.java | 19 +--- .../java/io/grpc/xds/XdsClientImplTestV2.java | 18 +--- .../test/java/io/grpc/xds/XdsClientTest.java | 59 ----------- .../java/io/grpc/xds/XdsNameResolverTest.java | 52 ++++++++- 14 files changed, 284 insertions(+), 227 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/XdsChannelFactory.java create mode 100644 xds/src/test/java/io/grpc/xds/XdsChannelFactoryTest.java diff --git a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java index b009ea7d530..60652af299f 100644 --- a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java @@ -32,7 +32,6 @@ import io.grpc.internal.ObjectPool; import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.xds.Bootstrapper.BootstrapInfo; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EdsLoadBalancerProvider.EdsConfig; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.Locality; @@ -43,7 +42,7 @@ import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; -import io.grpc.xds.XdsClient.XdsChannelFactory; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsClientFactory; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; @@ -129,8 +128,10 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { xdsClientPool = attributes.get(XdsAttributes.XDS_CLIENT_POOL); if (xdsClientPool == null) { final BootstrapInfo bootstrapInfo; + final XdsChannel channel; try { bootstrapInfo = bootstrapper.readBootstrap(); + channel = channelFactory.createChannel(bootstrapInfo.getServers()); } catch (Exception e) { helper.updateBalancingState( TRANSIENT_FAILURE, @@ -139,24 +140,14 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { return; } - final List serverList = bootstrapInfo.getServers(); final Node node = bootstrapInfo.getNode(); - if (serverList.isEmpty()) { - helper.updateBalancingState( - TRANSIENT_FAILURE, - new ErrorPicker( - Status.UNAVAILABLE - .withDescription("No management server provided by bootstrap"))); - return; - } XdsClientFactory xdsClientFactory = new XdsClientFactory() { @Override XdsClient createXdsClient() { return new XdsClientImpl( helper.getAuthority(), - serverList, - channelFactory, + channel, node, helper.getSynchronizationContext(), helper.getScheduledExecutorService(), diff --git a/xds/src/main/java/io/grpc/xds/XdsChannelFactory.java b/xds/src/main/java/io/grpc/xds/XdsChannelFactory.java new file mode 100644 index 00000000000..2ba7a6f00b5 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsChannelFactory.java @@ -0,0 +1,100 @@ +/* + * 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 com.google.common.annotations.VisibleForTesting; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.alts.GoogleDefaultChannelBuilder; +import io.grpc.xds.Bootstrapper.ChannelCreds; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.XdsClient.XdsChannel; +import io.grpc.xds.XdsLogger.XdsLogLevel; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Factory for creating channels to xDS severs. + */ +abstract class XdsChannelFactory { + @VisibleForTesting + static boolean experimentalV3SupportEnvVar = Boolean.parseBoolean( + System.getenv("GRPC_XDS_EXPERIMENTAL_V3_SUPPORT")); + + private static final String XDS_V3_SERVER_FEATURE = "xds_v3"; + private static final XdsChannelFactory DEFAULT_INSTANCE = new XdsChannelFactory() { + /** + * Creates a channel to the first server in the given list. + */ + @Override + XdsChannel createChannel(List servers) throws XdsInitializationException { + if (servers.isEmpty()) { + throw new XdsInitializationException("No server provided"); + } + XdsLogger logger = XdsLogger.withPrefix("xds-client-channel-factory"); + ServerInfo serverInfo = servers.get(0); + String serverUri = serverInfo.getServerUri(); + logger.log(XdsLogLevel.INFO, "Creating channel to {0}", serverUri); + List channelCredsList = serverInfo.getChannelCredentials(); + ManagedChannelBuilder channelBuilder = null; + // Use the first supported channel credentials configuration. + for (ChannelCreds creds : channelCredsList) { + switch (creds.getType()) { + case "google_default": + logger.log(XdsLogLevel.INFO, "Using channel credentials: google_default"); + channelBuilder = GoogleDefaultChannelBuilder.forTarget(serverUri); + break; + case "insecure": + logger.log(XdsLogLevel.INFO, "Using channel credentials: insecure"); + channelBuilder = ManagedChannelBuilder.forTarget(serverUri).usePlaintext(); + break; + case "tls": + logger.log(XdsLogLevel.INFO, "Using channel credentials: tls"); + channelBuilder = ManagedChannelBuilder.forTarget(serverUri); + break; + default: + } + if (channelBuilder != null) { + break; + } + } + if (channelBuilder == null) { + throw new XdsInitializationException("No server with supported channel creds found"); + } + + ManagedChannel channel = channelBuilder + .keepAliveTime(5, TimeUnit.MINUTES) + .build(); + boolean useProtocolV3 = experimentalV3SupportEnvVar + && serverInfo.getServerFeatures().contains(XDS_V3_SERVER_FEATURE); + + return new XdsChannel(channel, useProtocolV3); + } + }; + + static XdsChannelFactory getInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Creates a channel to one of the provided management servers. + * + * @throws XdsInitializationException if failed to create a channel with the given list of + * servers. + */ + abstract XdsChannel createChannel(List servers) throws XdsInitializationException; +} diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 7efb2a2937e..3e639c7fa3d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -16,7 +16,6 @@ package io.grpc.xds; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; @@ -25,13 +24,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.Status; -import io.grpc.alts.GoogleDefaultChannelBuilder; import io.grpc.internal.ObjectPool; -import io.grpc.xds.Bootstrapper.BootstrapInfo; -import io.grpc.xds.Bootstrapper.ChannelCreds; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; @@ -39,7 +33,6 @@ import io.grpc.xds.EnvoyServerProtoData.Listener; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager.LoadStatsStore; -import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -47,7 +40,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** @@ -613,62 +605,6 @@ public synchronized XdsClient returnObject(Object object) { } } - /** - * Factory for creating channels to xDS severs. - */ - abstract static class XdsChannelFactory { - @VisibleForTesting - static boolean experimentalV3SupportEnvVar = Boolean.parseBoolean( - System.getenv("GRPC_XDS_EXPERIMENTAL_V3_SUPPORT")); - - private static final String XDS_V3_SERVER_FEATURE = "xds_v3"; - private static final XdsChannelFactory DEFAULT_INSTANCE = new XdsChannelFactory() { - /** - * Creates a channel to the first server in the given list. - */ - @Override - XdsChannel createChannel(List servers) { - checkArgument(!servers.isEmpty(), "No management server provided."); - XdsLogger logger = XdsLogger.withPrefix("xds-client-channel-factory"); - ServerInfo serverInfo = servers.get(0); - String serverUri = serverInfo.getServerUri(); - logger.log(XdsLogLevel.INFO, "Creating channel to {0}", serverUri); - List channelCredsList = serverInfo.getChannelCredentials(); - ManagedChannelBuilder channelBuilder = null; - // Use the first supported channel credentials configuration. - // Currently, only "google_default" is supported. - for (ChannelCreds creds : channelCredsList) { - if (creds.getType().equals("google_default")) { - logger.log(XdsLogLevel.INFO, "Using channel credentials: google_default"); - channelBuilder = GoogleDefaultChannelBuilder.forTarget(serverUri); - break; - } - } - if (channelBuilder == null) { - logger.log(XdsLogLevel.INFO, "Using default channel credentials"); - channelBuilder = ManagedChannelBuilder.forTarget(serverUri); - } - - ManagedChannel channel = channelBuilder - .keepAliveTime(5, TimeUnit.MINUTES) - .build(); - boolean useProtocolV3 = experimentalV3SupportEnvVar - && serverInfo.getServerFeatures().contains(XDS_V3_SERVER_FEATURE); - - return new XdsChannel(channel, useProtocolV3); - } - }; - - static XdsChannelFactory getInstance() { - return DEFAULT_INSTANCE; - } - - /** - * Creates a channel to one of the provided management servers. - */ - abstract XdsChannel createChannel(List servers); - } - static final class XdsChannel { private final ManagedChannel managedChannel; private final boolean useProtocolV3; @@ -687,8 +623,4 @@ boolean isUseProtocolV3() { return useProtocolV3; } } - - interface XdsClientPoolFactory { - ObjectPool newXdsClientObjectPool(BootstrapInfo bootstrapInfo); - } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 1dcb0c656cc..829e8711795 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -53,7 +53,6 @@ import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; @@ -204,18 +203,14 @@ final class XdsClientImpl extends XdsClient { XdsClientImpl( String targetName, - List servers, // list of management servers - XdsChannelFactory channelFactory, + XdsChannel channel, Node node, SynchronizationContext syncContext, ScheduledExecutorService timeService, BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { this.targetName = checkNotNull(targetName, "targetName"); - XdsChannel xdsChannel = - checkNotNull(channelFactory, "channelFactory") - .createChannel(checkNotNull(servers, "servers")); - this.xdsChannel = xdsChannel; + this.xdsChannel = checkNotNull(channel, "channel"); this.node = checkNotNull(node, "node"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timeService = checkNotNull(timeService, "timeService"); diff --git a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java index e79726e8a1b..08742d419bd 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java @@ -33,6 +33,7 @@ import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.FilterChain; import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; +import io.grpc.xds.XdsClient.XdsChannel; import io.netty.channel.Channel; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; @@ -120,13 +121,14 @@ public boolean hasXdsClient() { public void createXdsClientAndStart() throws IOException { checkState(xdsClient == null, "start() called more than once"); Bootstrapper.BootstrapInfo bootstrapInfo; - List serverList; + XdsChannel channel; try { bootstrapInfo = Bootstrapper.getInstance().readBootstrap(); - serverList = bootstrapInfo.getServers(); + List serverList = bootstrapInfo.getServers(); if (serverList.isEmpty()) { throw new XdsInitializationException("No management server provided by bootstrap"); } + channel = XdsChannelFactory.getInstance().createChannel(serverList); } catch (XdsInitializationException e) { reportError(Status.fromThrowable(e)); throw new IOException(e); @@ -136,8 +138,7 @@ public void createXdsClientAndStart() throws IOException { XdsClientImpl xdsClientImpl = new XdsClientImpl( "", - serverList, - XdsClient.XdsChannelFactory.getInstance(), + channel, node, createSynchronizationContext(), timeService, diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index f6e00cd85fb..534ac7f86fa 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -39,8 +39,9 @@ import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; import io.grpc.xds.XdsClient.ConfigUpdate; import io.grpc.xds.XdsClient.ConfigWatcher; -import io.grpc.xds.XdsClient.XdsClientPoolFactory; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsLogger.XdsLogLevel; +import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -70,6 +71,7 @@ final class XdsNameResolver extends NameResolver { private final ServiceConfigParser serviceConfigParser; private final SynchronizationContext syncContext; private final Bootstrapper bootstrapper; + private final XdsChannelFactory channelFactory; private final XdsClientPoolFactory xdsClientPoolFactory; private final ThreadSafeRandom random; private final ConcurrentMap clusterRefs = new ConcurrentHashMap<>(); @@ -85,20 +87,23 @@ final class XdsNameResolver extends NameResolver { SynchronizationContext syncContext, XdsClientPoolFactory xdsClientPoolFactory) { this(name, serviceConfigParser, syncContext, Bootstrapper.getInstance(), - xdsClientPoolFactory, ThreadSafeRandomImpl.instance); + XdsChannelFactory.getInstance(), xdsClientPoolFactory, ThreadSafeRandomImpl.instance); } + @VisibleForTesting XdsNameResolver( String name, ServiceConfigParser serviceConfigParser, SynchronizationContext syncContext, Bootstrapper bootstrapper, + XdsChannelFactory channelFactory, XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random) { authority = GrpcUtil.checkAuthority(checkNotNull(name, "name")); this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.bootstrapper = checkNotNull(bootstrapper, "bootstrapper"); + this.channelFactory = checkNotNull(channelFactory, "channelFactory"); this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory"); this.random = checkNotNull(random, "random"); logger = XdsLogger.withLogId(InternalLogId.allocate("xds-resolver", name)); @@ -114,14 +119,16 @@ public String getServiceAuthority() { public void start(Listener2 listener) { this.listener = checkNotNull(listener, "listener"); BootstrapInfo bootstrapInfo; + XdsChannel channel; try { bootstrapInfo = bootstrapper.readBootstrap(); + channel = channelFactory.createChannel(bootstrapInfo.getServers()); } catch (Exception e) { listener.onError( - Status.UNAVAILABLE.withDescription("Failed to load xDS bootstrap").withCause(e)); + Status.UNAVAILABLE.withDescription("Failed to initialize xDS").withCause(e)); return; } - xdsClientPool = xdsClientPoolFactory.newXdsClientObjectPool(bootstrapInfo); + xdsClientPool = xdsClientPoolFactory.newXdsClientObjectPool(bootstrapInfo, channel); xdsClient = xdsClientPool.getObject(); xdsClient.watchConfigData(authority, new ConfigWatcherImpl()); } diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java index d00929cc38f..ee7c1d68a01 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java @@ -31,9 +31,8 @@ import io.grpc.internal.ObjectPool; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; -import io.grpc.xds.XdsClient.XdsChannelFactory; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsClientFactory; -import io.grpc.xds.XdsClient.XdsClientPoolFactory; import java.net.URI; import java.util.concurrent.ScheduledExecutorService; @@ -64,11 +63,8 @@ public XdsNameResolver newNameResolver(URI targetUri, Args args) { String name = targetPath.substring(1); XdsClientPoolFactory xdsClientPoolFactory = new RefCountedXdsClientPoolFactory( - name, - XdsChannelFactory.getInstance(), - args.getSynchronizationContext(), args.getScheduledExecutorService(), - new ExponentialBackoffPolicy.Provider(), - GrpcUtil.STOPWATCH_SUPPLIER); + name, args.getSynchronizationContext(), args.getScheduledExecutorService(), + new ExponentialBackoffPolicy.Provider(), GrpcUtil.STOPWATCH_SUPPLIER); return new XdsNameResolver( name, args.getServiceConfigParser(), args.getSynchronizationContext(), xdsClientPoolFactory); @@ -95,7 +91,6 @@ protected int priority() { static class RefCountedXdsClientPoolFactory implements XdsClientPoolFactory { private final String serviceName; - private final XdsChannelFactory channelFactory; private final SynchronizationContext syncContext; private final ScheduledExecutorService timeService; private final BackoffPolicy.Provider backoffPolicyProvider; @@ -103,13 +98,11 @@ static class RefCountedXdsClientPoolFactory implements XdsClientPoolFactory { RefCountedXdsClientPoolFactory( String serviceName, - XdsChannelFactory channelFactory, SynchronizationContext syncContext, ScheduledExecutorService timeService, BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { this.serviceName = checkNotNull(serviceName, "serviceName"); - this.channelFactory = checkNotNull(channelFactory, "channelFactory"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timeService = checkNotNull(timeService, "timeService"); this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); @@ -117,16 +110,21 @@ static class RefCountedXdsClientPoolFactory implements XdsClientPoolFactory { } @Override - public ObjectPool newXdsClientObjectPool(final BootstrapInfo bootstrapInfo) { + public ObjectPool newXdsClientObjectPool( + final BootstrapInfo bootstrapInfo, final XdsChannel channel) { XdsClientFactory xdsClientFactory = new XdsClientFactory() { @Override XdsClient createXdsClient() { return new XdsClientImpl( - serviceName, bootstrapInfo.getServers(), channelFactory, bootstrapInfo.getNode(), - syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); + serviceName, channel, bootstrapInfo.getNode(), syncContext, timeService, + backoffPolicyProvider, stopwatchSupplier); } }; return new RefCountedXdsClientObjectPool(xdsClientFactory); } } + + interface XdsClientPoolFactory { + ObjectPool newXdsClientObjectPool(BootstrapInfo bootstrapInfo, XdsChannel channel); + } } diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index 7461159eaa9..192201e7a94 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -80,7 +80,6 @@ import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.XdsChannel; -import io.grpc.xds.XdsClient.XdsChannelFactory; import java.net.InetSocketAddress; import java.util.ArrayDeque; import java.util.ArrayList; @@ -240,8 +239,7 @@ public StreamObserver streamAggregatedResources( xdsClientPoolFromResolveAddresses = new FakeXdsClientPool( new XdsClientImpl( SERVICE_AUTHORITY, - serverList, - channelFactory, + new XdsChannel(channel, /* useProtocolV3= */ false), node, syncContext, fakeClock.getScheduledExecutorService(), diff --git a/xds/src/test/java/io/grpc/xds/XdsChannelFactoryTest.java b/xds/src/test/java/io/grpc/xds/XdsChannelFactoryTest.java new file mode 100644 index 00000000000..090f6ab94ec --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/XdsChannelFactoryTest.java @@ -0,0 +1,99 @@ +/* + * 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.junit.Assert.fail; + +import io.grpc.xds.Bootstrapper.ChannelCreds; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.XdsClient.XdsChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link XdsChannelFactory}. + */ +@RunWith(JUnit4.class) +public class XdsChannelFactoryTest { + + private final XdsChannelFactory channelFactory = XdsChannelFactory.getInstance(); + private final List channels = new ArrayList<>(); + private ServerInfo server1; // google_default + private ServerInfo server2; // plaintext, v3 + private ServerInfo server3; // unsupported + + @Before + public void setUp() { + ChannelCreds googleDefault = new ChannelCreds("google_default", null); + ChannelCreds insecure = new ChannelCreds("insecure", null); + ChannelCreds unsupported = new ChannelCreds("unsupported", null); + server1 = new ServerInfo("server1.com", Collections.singletonList(googleDefault), + Collections.emptyList()); + server2 = new ServerInfo("server2.com", Collections.singletonList(insecure), + Collections.singletonList("xds_v3")); + server3 = new ServerInfo("server4.com", Collections.singletonList(unsupported), + Collections.emptyList()); + } + + @After + public void tearDown() { + for (XdsChannel channel : channels) { + channel.getManagedChannel().shutdown(); + } + } + + @Test + public void failToCreateChannel_unsupportedChannelCreds() { + try { + createChannel(server3); + fail("Should have thrown"); + } catch (XdsInitializationException expected) { + } + } + + @Test + public void defaultUseV2ProtocolL() throws XdsInitializationException { + XdsChannel channel = createChannel(server1); + assertThat(channel.isUseProtocolV3()).isFalse(); + } + + @Test + public void supportServerFeature_v3Protocol() throws XdsInitializationException { + boolean originalV3SupportEnvVar = XdsChannelFactory.experimentalV3SupportEnvVar; + XdsChannelFactory.experimentalV3SupportEnvVar = true; + try { + XdsChannel channel = createChannel(server2); + assertThat(channel.isUseProtocolV3()).isTrue(); + } finally { + XdsChannelFactory.experimentalV3SupportEnvVar = originalV3SupportEnvVar; + } + } + + private XdsChannel createChannel(ServerInfo... servers) throws XdsInitializationException { + XdsChannel channel = channelFactory.createChannel(Arrays.asList(servers)); + channels.add(channel); + return channel; + } +} diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index a9b8e42b6bb..f75b67fccd2 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -86,8 +86,6 @@ import io.grpc.internal.FakeClock.TaskFilter; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import io.grpc.xds.Bootstrapper.ChannelCreds; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; @@ -100,7 +98,6 @@ import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; import io.grpc.xds.XdsClient.XdsChannel; -import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.XdsClientImpl.MessagePrinter; import java.io.IOException; import java.util.ArrayDeque; @@ -279,23 +276,10 @@ public void cancelled(Context context) { channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - List servers = - ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); - XdsChannelFactory channelFactory = new XdsChannelFactory() { - @Override - XdsChannel createChannel(List servers) { - ServerInfo serverInfo = Iterables.getOnlyElement(servers); - assertThat(serverInfo.getServerUri()).isEqualTo(serverName); - assertThat(serverInfo.getChannelCredentials()).isEmpty(); - return new XdsChannel(channel, /* useProtocolV3= */ true); - } - }; - xdsClient = new XdsClientImpl( TARGET_AUTHORITY, - servers, - channelFactory, + new XdsChannel(channel, /* useProtocolV3= */ true), EnvoyProtoData.Node.newBuilder().build(), syncContext, fakeClock.getScheduledExecutorService(), diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java index 77c073cf7b6..c4028eec348 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java @@ -64,15 +64,12 @@ import io.grpc.internal.FakeClock.TaskFilter; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import io.grpc.xds.Bootstrapper.ChannelCreds; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.Address; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.ConfigWatcher; import io.grpc.xds.XdsClient.ListenerUpdate; import io.grpc.xds.XdsClient.ListenerWatcher; import io.grpc.xds.XdsClient.XdsChannel; -import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; import java.io.IOException; import java.util.ArrayDeque; @@ -200,21 +197,9 @@ public void cancelled(Context context) { channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - List servers = - ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); - XdsChannelFactory channelFactory = new XdsChannelFactory() { - @Override - XdsChannel createChannel(List servers) { - ServerInfo serverInfo = Iterables.getOnlyElement(servers); - assertThat(serverInfo.getServerUri()).isEqualTo(serverName); - assertThat(serverInfo.getChannelCredentials()).isEmpty(); - return new XdsChannel(channel, false); - } - }; - xdsClient = - new XdsClientImpl("", servers, channelFactory, NODE, syncContext, - fakeClock.getScheduledExecutorService(), backoffPolicyProvider, + new XdsClientImpl("", new XdsChannel(channel, /* useProtocolV3= */ false), NODE, + syncContext, fakeClock.getScheduledExecutorService(), backoffPolicyProvider, fakeClock.getStopwatchSupplier()); // Only the connection to management server is established, no RPC request is sent until at // least one watcher is registered. diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index 51b5b5db684..17351d00d17 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -86,8 +86,6 @@ import io.grpc.internal.FakeClock.TaskFilter; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import io.grpc.xds.Bootstrapper.ChannelCreds; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; @@ -100,7 +98,6 @@ import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; import io.grpc.xds.XdsClient.XdsChannel; -import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.XdsClientImpl.MessagePrinter; import java.io.IOException; import java.util.ArrayDeque; @@ -278,23 +275,10 @@ public void cancelled(Context context) { channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - List servers = - ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); - XdsChannelFactory channelFactory = new XdsChannelFactory() { - @Override - XdsChannel createChannel(List servers) { - ServerInfo serverInfo = Iterables.getOnlyElement(servers); - assertThat(serverInfo.getServerUri()).isEqualTo(serverName); - assertThat(serverInfo.getChannelCredentials()).isEmpty(); - return new XdsChannel(channel, false); - } - }; - xdsClient = new XdsClientImpl( TARGET_AUTHORITY, - servers, - channelFactory, + new XdsChannel(channel, /* useProtocolV3= */ false), Node.newBuilder().build(), syncContext, fakeClock.getScheduledExecutorService(), diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTest.java b/xds/src/test/java/io/grpc/xds/XdsClientTest.java index 9278a80dbb5..56fca6dc09b 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTest.java @@ -21,12 +21,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import com.google.common.collect.ImmutableList; -import io.grpc.xds.Bootstrapper.ChannelCreds; -import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; -import io.grpc.xds.XdsClient.XdsChannel; -import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.XdsClient.XdsClientFactory; import org.junit.Rule; import org.junit.Test; @@ -107,58 +102,4 @@ XdsClient createXdsClient() { XdsClient xdsClient2 = xdsClientPool.getObject(); assertThat(xdsClient2).isNotSameInstanceAs(xdsClient1); } - - @Test - public void channelFactorySupportsV3() { - boolean originalV3SupportEnvVar = XdsChannelFactory.experimentalV3SupportEnvVar; - try { - XdsChannelFactory xdsChannelFactory = XdsChannelFactory.getInstance(); - XdsChannelFactory.experimentalV3SupportEnvVar = true; - XdsChannel xdsChannel = - xdsChannelFactory.createChannel( - ImmutableList.of( - new ServerInfo( - "xdsserver.com", - ImmutableList.of(), - ImmutableList.of()), - new ServerInfo( - "xdsserver2.com", - ImmutableList.of(), - ImmutableList.of("xds_v3")))); - xdsChannel.getManagedChannel().shutdown(); - assertThat(xdsChannel.isUseProtocolV3()).isFalse(); - - XdsChannelFactory.experimentalV3SupportEnvVar = false; - xdsChannel = - xdsChannelFactory.createChannel( - ImmutableList.of( - new ServerInfo( - "xdsserver.com", - ImmutableList.of(), - ImmutableList.of("xds_v3")), - new ServerInfo( - "xdsserver2.com", - ImmutableList.of(), - ImmutableList.of("baz")))); - xdsChannel.getManagedChannel().shutdown(); - assertThat(xdsChannel.isUseProtocolV3()).isFalse(); - - XdsChannelFactory.experimentalV3SupportEnvVar = true; - xdsChannel = - xdsChannelFactory.createChannel( - ImmutableList.of( - new ServerInfo( - "xdsserver.com", - ImmutableList.of(), - ImmutableList.of("xds_v3")), - new ServerInfo( - "xdsserver2.com", - ImmutableList.of(), - ImmutableList.of("baz")))); - xdsChannel.getManagedChannel().shutdown(); - assertThat(xdsChannel.isUseProtocolV3()).isTrue(); - } finally { - XdsChannelFactory.experimentalV3SupportEnvVar = originalV3SupportEnvVar; - } - } } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 30658a045fe..797bd45a561 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -30,6 +31,7 @@ import io.grpc.CallOptions; import io.grpc.InternalConfigSelector; import io.grpc.InternalConfigSelector.Result; +import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.MethodDescriptor.MethodType; @@ -46,11 +48,13 @@ import io.grpc.internal.PickSubchannelArgsImpl; import io.grpc.testing.TestMethodDescriptors; import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.ClusterWeight; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.EnvoyProtoData.RouteAction; -import io.grpc.xds.XdsClient.XdsClientPoolFactory; +import io.grpc.xds.XdsClient.XdsChannel; +import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -88,6 +92,12 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { return ConfigOrError.fromConfig(rawServiceConfig); } }; + private final XdsChannelFactory channelFactory = new XdsChannelFactory() { + @Override + XdsChannel createChannel(List servers) throws XdsInitializationException { + return new XdsChannel(mock(ManagedChannel.class), false); + } + }; private final FakeXdsClientPoolFactory xdsClientPoolFactory = new FakeXdsClientPoolFactory(); private final String cluster1 = "cluster-foo.googleapis.com"; private final String cluster2 = "cluster-bar.googleapis.com"; @@ -119,7 +129,7 @@ public BootstrapInfo readBootstrap() { } }; resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, bootstrapper, - xdsClientPoolFactory, mockRandom); + channelFactory, xdsClientPoolFactory, mockRandom); } @Test @@ -131,15 +141,46 @@ public BootstrapInfo readBootstrap() throws XdsInitializationException { } }; resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, bootstrapper, - xdsClientPoolFactory, mockRandom); + channelFactory, xdsClientPoolFactory, mockRandom); resolver.start(mockListener); verify(mockListener).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); - assertThat(error.getDescription()).isEqualTo("Failed to load xDS bootstrap"); + assertThat(error.getDescription()).isEqualTo("Failed to initialize xDS"); assertThat(error.getCause()).hasMessageThat().isEqualTo("Fail to read bootstrap file"); } + @Test + public void resolve_failToCreateXdsChannel() { + Bootstrapper bootstrapper = new Bootstrapper() { + @Override + public BootstrapInfo readBootstrap() { + return new BootstrapInfo( + ImmutableList.of( + new ServerInfo( + "trafficdirector.googleapis.com", + ImmutableList.of(), ImmutableList.of())), + Node.newBuilder().build(), + null); + } + }; + XdsChannelFactory channelFactory = new XdsChannelFactory() { + @Override + XdsChannel createChannel(List servers) throws XdsInitializationException { + throw new XdsInitializationException("No server with supported channel creds found"); + } + }; + resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, bootstrapper, + channelFactory, xdsClientPoolFactory, mockRandom); + resolver.start(mockListener); + verify(mockListener).onError(errorCaptor.capture()); + Status error = errorCaptor.getValue(); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("Failed to initialize xDS"); + assertThat(error.getCause()).hasMessageThat() + .isEqualTo("No server with supported channel creds found"); + } + @SuppressWarnings("unchecked") @Test public void resolve_resourceNotFound() { @@ -472,7 +513,8 @@ public void generateServiceConfig_forMethodTimeoutConfig() throws IOException { private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { @Override - public ObjectPool newXdsClientObjectPool(BootstrapInfo bootstrapInfo) { + public ObjectPool newXdsClientObjectPool( + BootstrapInfo bootstrapInfo, XdsChannel channel) { return new ObjectPool() { @Override public XdsClient getObject() {