mirror of https://github.com/grpc/grpc-java.git
xds: implement cloud-to-prod resolver (#7900)
Implemented CloudToProdNameResolver, which will be used for DirectPath with URI scheme "google-c2p". The resolver is only a wrapper that delegates name resolution either to DNS or xDS resolver depending on the environment. If it is delegating to the xDS resolver, it will send HTTP requests (to a local HTTP server) to fetch metadata that is used to generate a bootstrap config. The self-generated bootstrap will be used for xDS.
This commit is contained in:
parent
bfc67bfcf4
commit
2bfa0037ad
|
@ -56,7 +56,10 @@ import net.ltgt.gradle.errorprone.CheckSeverity
|
|||
it.options.errorprone.check("FutureReturnValueIgnored", CheckSeverity.OFF)
|
||||
}
|
||||
|
||||
javadoc { exclude 'io/grpc/alts/internal/**' }
|
||||
javadoc {
|
||||
exclude 'io/grpc/alts/internal/**'
|
||||
exclude 'io/grpc/alts/Internal*'
|
||||
}
|
||||
|
||||
jar {
|
||||
// Must use a different classifier to avoid conflicting with shadowJar
|
||||
|
|
|
@ -93,7 +93,7 @@ public final class AltsChannelCredentials {
|
|||
}
|
||||
|
||||
InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() {
|
||||
if (!CheckGcpEnvironment.isOnGcp()) {
|
||||
if (!InternalCheckGcpEnvironment.isOnGcp()) {
|
||||
if (enableUntrustedAlts) {
|
||||
logger.log(
|
||||
Level.WARNING,
|
||||
|
|
|
@ -76,7 +76,7 @@ public final class AltsServerCredentials {
|
|||
}
|
||||
|
||||
InternalProtocolNegotiator.ProtocolNegotiator buildProtocolNegotiator() {
|
||||
if (!CheckGcpEnvironment.isOnGcp()) {
|
||||
if (!InternalCheckGcpEnvironment.isOnGcp()) {
|
||||
if (enableUntrustedAlts) {
|
||||
logger.log(
|
||||
Level.WARNING,
|
||||
|
|
|
@ -49,7 +49,7 @@ public final class ComputeEngineChannelCredentials {
|
|||
ChannelCredentials nettyCredentials =
|
||||
InternalNettyChannelCredentials.create(createClientFactory());
|
||||
CallCredentials callCredentials;
|
||||
if (CheckGcpEnvironment.isOnGcp()) {
|
||||
if (InternalCheckGcpEnvironment.isOnGcp()) {
|
||||
callCredentials = MoreCallCredentials.from(ComputeEngineCredentials.create());
|
||||
} else {
|
||||
callCredentials = new FailingCallCredentials(
|
||||
|
|
|
@ -19,6 +19,7 @@ package io.grpc.alts;
|
|||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.grpc.Internal;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
@ -28,18 +29,27 @@ import java.util.Locale;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** Class for checking if the system is running on Google Cloud Platform (GCP). */
|
||||
final class CheckGcpEnvironment {
|
||||
/**
|
||||
* Class for checking if the system is running on Google Cloud Platform (GCP).
|
||||
* This is intended for usage internal to the gRPC team. If you *really* think you need
|
||||
* to use this, contact the gRPC team first.
|
||||
*/
|
||||
@Internal
|
||||
public final class InternalCheckGcpEnvironment {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CheckGcpEnvironment.class.getName());
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(InternalCheckGcpEnvironment.class.getName());
|
||||
private static final String DMI_PRODUCT_NAME = "/sys/class/dmi/id/product_name";
|
||||
private static final String WINDOWS_COMMAND = "powershell.exe";
|
||||
private static Boolean cachedResult = null;
|
||||
|
||||
// Construct me not!
|
||||
private CheckGcpEnvironment() {}
|
||||
private InternalCheckGcpEnvironment() {}
|
||||
|
||||
static synchronized boolean isOnGcp() {
|
||||
/**
|
||||
* Returns {@code true} if currently running on Google Cloud Platform (GCP).
|
||||
*/
|
||||
public static synchronized boolean isOnGcp() {
|
||||
if (cachedResult == null) {
|
||||
cachedResult = isRunningOnGcp();
|
||||
}
|
|
@ -26,37 +26,37 @@ import org.junit.runner.RunWith;
|
|||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class CheckGcpEnvironmentTest {
|
||||
public final class InternalCheckGcpEnvironmentTest {
|
||||
|
||||
@Test
|
||||
public void checkGcpLinuxPlatformData() throws Exception {
|
||||
BufferedReader reader;
|
||||
reader = new BufferedReader(new StringReader("HP Z440 Workstation"));
|
||||
assertFalse(CheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
assertFalse(InternalCheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
reader = new BufferedReader(new StringReader("Google"));
|
||||
assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
reader = new BufferedReader(new StringReader("Google Compute Engine"));
|
||||
assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
reader = new BufferedReader(new StringReader("Google Compute Engine "));
|
||||
assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkProductNameOnLinux(reader));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkGcpWindowsPlatformData() throws Exception {
|
||||
BufferedReader reader;
|
||||
reader = new BufferedReader(new StringReader("Product : Google"));
|
||||
assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertFalse(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("Manufacturer : LENOVO"));
|
||||
assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertFalse(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("Manufacturer : Google Compute Engine"));
|
||||
assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertFalse(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("Manufacturer : Google"));
|
||||
assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("Manufacturer:Google"));
|
||||
assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("Manufacturer : Google "));
|
||||
assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
reader = new BufferedReader(new StringReader("BIOSVersion : 1.0\nManufacturer : Google\n"));
|
||||
assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
assertTrue(InternalCheckGcpEnvironment.checkBiosDataOnWindows(reader));
|
||||
}
|
||||
}
|
|
@ -31,19 +31,26 @@ import javax.annotation.Nullable;
|
|||
* Loads configuration information to bootstrap gRPC's integration of xDS protocol.
|
||||
*/
|
||||
@Internal
|
||||
public interface Bootstrapper {
|
||||
public abstract class Bootstrapper {
|
||||
|
||||
/**
|
||||
* Returns configurations from bootstrap.
|
||||
* Returns system-loaded bootstrap configuration.
|
||||
*/
|
||||
BootstrapInfo bootstrap() throws XdsInitializationException;
|
||||
public abstract BootstrapInfo bootstrap() throws XdsInitializationException;
|
||||
|
||||
/**
|
||||
* Returns bootstrap configuration given by the raw data in JSON format.
|
||||
*/
|
||||
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class containing xDS server information, such as server URI and channel credentials
|
||||
* to be used for communication.
|
||||
*/
|
||||
@Internal
|
||||
class ServerInfo {
|
||||
static class ServerInfo {
|
||||
private final String target;
|
||||
private final ChannelCredentials channelCredentials;
|
||||
private final boolean useProtocolV3;
|
||||
|
@ -73,7 +80,7 @@ public interface Bootstrapper {
|
|||
* Map that represents the config for that plugin.
|
||||
*/
|
||||
@Internal
|
||||
class CertificateProviderInfo {
|
||||
public static class CertificateProviderInfo {
|
||||
private final String pluginName;
|
||||
private final Map<String, ?> config;
|
||||
|
||||
|
@ -95,7 +102,7 @@ public interface Bootstrapper {
|
|||
* Data class containing the results of reading bootstrap.
|
||||
*/
|
||||
@Internal
|
||||
class BootstrapInfo {
|
||||
public static class BootstrapInfo {
|
||||
private List<ServerInfo> servers;
|
||||
private final Node node;
|
||||
@Nullable private final Map<String, CertificateProviderInfo> certProviders;
|
||||
|
|
|
@ -43,7 +43,7 @@ import javax.annotation.Nullable;
|
|||
* A {@link Bootstrapper} implementation that reads xDS configurations from local file system.
|
||||
*/
|
||||
@Internal
|
||||
public class BootstrapperImpl implements Bootstrapper {
|
||||
public class BootstrapperImpl extends Bootstrapper {
|
||||
|
||||
private static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP";
|
||||
@VisibleForTesting
|
||||
|
@ -80,25 +80,24 @@ public class BootstrapperImpl implements Bootstrapper {
|
|||
* <li>Java System Property value of "io.grpc.xds.bootstrap_value"</li>
|
||||
* </ol>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public BootstrapInfo bootstrap() throws XdsInitializationException {
|
||||
String filePath =
|
||||
bootstrapPathFromEnvVar != null ? bootstrapPathFromEnvVar : bootstrapPathFromSysProp;
|
||||
String rawBootstrap;
|
||||
String fileContent;
|
||||
if (filePath != null) {
|
||||
logger.log(XdsLogLevel.INFO, "Reading bootstrap file from {0}", filePath);
|
||||
try {
|
||||
rawBootstrap = reader.readFile(filePath);
|
||||
fileContent = reader.readFile(filePath);
|
||||
} catch (IOException e) {
|
||||
throw new XdsInitializationException("Fail to read bootstrap file", e);
|
||||
}
|
||||
} else {
|
||||
rawBootstrap = bootstrapConfigFromEnvVar != null
|
||||
fileContent = bootstrapConfigFromEnvVar != null
|
||||
? bootstrapConfigFromEnvVar : bootstrapConfigFromSysProp;
|
||||
}
|
||||
if (rawBootstrap != null) {
|
||||
return parseConfig(rawBootstrap);
|
||||
}
|
||||
if (fileContent == null) {
|
||||
throw new XdsInitializationException(
|
||||
"Cannot find bootstrap configuration\n"
|
||||
+ "Environment variables searched:\n"
|
||||
|
@ -109,41 +108,21 @@ public class BootstrapperImpl implements Bootstrapper {
|
|||
+ "- " + BOOTSTRAP_CONFIG_SYS_PROPERTY_VAR + "\n\n");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setFileReader(FileReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content of the file with the given path in the file system.
|
||||
*/
|
||||
interface FileReader {
|
||||
String readFile(String path) throws IOException;
|
||||
}
|
||||
|
||||
private enum LocalFileReader implements FileReader {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String readFile(String path) throws IOException {
|
||||
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses a raw string into {@link BootstrapInfo}. */
|
||||
@SuppressWarnings("unchecked")
|
||||
private BootstrapInfo parseConfig(String rawData) throws XdsInitializationException {
|
||||
logger.log(XdsLogLevel.INFO, "Reading bootstrap information");
|
||||
logger.log(XdsLogLevel.INFO, "Reading bootstrap from " + filePath);
|
||||
Map<String, ?> rawBootstrap;
|
||||
try {
|
||||
rawBootstrap = (Map<String, ?>) JsonParser.parse(rawData);
|
||||
rawBootstrap = (Map<String, ?>) JsonParser.parse(fileContent);
|
||||
} catch (IOException e) {
|
||||
throw new XdsInitializationException("Failed to parse JSON", e);
|
||||
}
|
||||
logger.log(XdsLogLevel.DEBUG, "Bootstrap configuration:\n{0}", rawBootstrap);
|
||||
return bootstrap(rawBootstrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
|
||||
List<ServerInfo> servers = new ArrayList<>();
|
||||
List<?> rawServerConfigs = JsonUtil.getList(rawBootstrap, "xds_servers");
|
||||
List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
|
||||
if (rawServerConfigs == null) {
|
||||
throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
|
||||
}
|
||||
|
@ -179,7 +158,7 @@ public class BootstrapperImpl implements Bootstrapper {
|
|||
}
|
||||
|
||||
Node.Builder nodeBuilder = Node.newBuilder();
|
||||
Map<String, ?> rawNode = JsonUtil.getObject(rawBootstrap, "node");
|
||||
Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
|
||||
if (rawNode != null) {
|
||||
String id = JsonUtil.getString(rawNode, "id");
|
||||
if (id != null) {
|
||||
|
@ -222,7 +201,7 @@ public class BootstrapperImpl implements Bootstrapper {
|
|||
nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion());
|
||||
nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING);
|
||||
|
||||
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawBootstrap, "certificate_providers");
|
||||
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
|
||||
Map<String, CertificateProviderInfo> certProviders = null;
|
||||
if (certProvidersBlob != null) {
|
||||
certProviders = new HashMap<>(certProvidersBlob.size());
|
||||
|
@ -236,11 +215,32 @@ public class BootstrapperImpl implements Bootstrapper {
|
|||
certProviders.put(name, certificateProviderInfo);
|
||||
}
|
||||
}
|
||||
String grpcServerResourceId = JsonUtil.getString(rawBootstrap, "grpc_server_resource_name_id");
|
||||
String grpcServerResourceId = JsonUtil.getString(rawData, "grpc_server_resource_name_id");
|
||||
return new BootstrapInfo(servers, nodeBuilder.build(), certProviders, grpcServerResourceId);
|
||||
}
|
||||
|
||||
static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
|
||||
@VisibleForTesting
|
||||
void setFileReader(FileReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content of the file with the given path in the file system.
|
||||
*/
|
||||
interface FileReader {
|
||||
String readFile(String path) throws IOException;
|
||||
}
|
||||
|
||||
private enum LocalFileReader implements FileReader {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String readFile(String path) throws IOException {
|
||||
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
|
||||
if (value == null) {
|
||||
throw new XdsInitializationException(
|
||||
"Invalid bootstrap: '" + fieldName + "' does not exist.");
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Copyright 2021 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.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.CharStreams;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.alts.InternalCheckGcpEnvironment;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.SharedResourceHolder;
|
||||
import io.grpc.internal.SharedResourceHolder.Resource;
|
||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* CloudToProd version of {@link NameResolver}.
|
||||
*/
|
||||
final class GoogleCloudToProdNameResolver extends NameResolver {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String METADATA_URL_ZONE =
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/zone";
|
||||
@VisibleForTesting
|
||||
static final String METADATA_URL_SUPPORT_IPV6 =
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s";
|
||||
@VisibleForTesting
|
||||
static boolean isOnGcp = InternalCheckGcpEnvironment.isOnGcp();
|
||||
@VisibleForTesting
|
||||
static boolean xdsBootstrapProvided =
|
||||
System.getenv("GRPC_XDS_BOOTSTRAP") != null
|
||||
|| System.getProperty("io.grpc.xds.bootstrap") != null
|
||||
|| System.getenv("GRPC_XDS_BOOTSTRAP_CONFIG") != null
|
||||
|| System.getProperty("io.grpc.xds.bootstrapValue") != null;
|
||||
|
||||
private HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
|
||||
private final String authority;
|
||||
private final SynchronizationContext syncContext;
|
||||
private final Resource<Executor> executorResource;
|
||||
private final XdsClientPoolFactory xdsClientPoolFactory;
|
||||
private final NameResolver delegate;
|
||||
private final boolean usingExecutorResource;
|
||||
// It's not possible to use both PSM and DirectPath C2P in the same application.
|
||||
// Delegate to DNS if user-provided bootstrap is found.
|
||||
private final String schemeOverride = !isOnGcp || xdsBootstrapProvided ? "dns" : "xds";
|
||||
private Executor executor;
|
||||
private Listener2 listener;
|
||||
private boolean succeeded;
|
||||
private boolean resolving;
|
||||
private boolean shutdown;
|
||||
|
||||
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
|
||||
XdsClientPoolFactory xdsClientPoolFactory) {
|
||||
this(targetUri, args, executorResource, xdsClientPoolFactory,
|
||||
NameResolverRegistry.getDefaultRegistry().asFactory());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
|
||||
XdsClientPoolFactory xdsClientPoolFactory, NameResolver.Factory nameResolverFactory) {
|
||||
this.executorResource = checkNotNull(executorResource, "executorResource");
|
||||
this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory");
|
||||
String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath");
|
||||
Preconditions.checkArgument(
|
||||
targetPath.startsWith("/"),
|
||||
"the path component (%s) of the target (%s) must start with '/'",
|
||||
targetPath,
|
||||
targetUri);
|
||||
authority = GrpcUtil.checkAuthority(targetPath.substring(1));
|
||||
syncContext = checkNotNull(args, "args").getSynchronizationContext();
|
||||
delegate = checkNotNull(nameResolverFactory, "nameResolverFactory").newNameResolver(
|
||||
overrideUriScheme(targetUri, schemeOverride), args);
|
||||
executor = args.getOffloadExecutor();
|
||||
usingExecutorResource = executor == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAuthority() {
|
||||
return authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final Listener2 listener) {
|
||||
if (delegate == null) {
|
||||
listener.onError(Status.INTERNAL.withDescription(
|
||||
"Delegate resolver not found, scheme: " + schemeOverride));
|
||||
return;
|
||||
}
|
||||
this.listener = checkNotNull(listener, "listener");
|
||||
resolve();
|
||||
}
|
||||
|
||||
private void resolve() {
|
||||
if (resolving || shutdown || delegate == null) {
|
||||
return;
|
||||
}
|
||||
resolving = true;
|
||||
if (schemeOverride.equals("dns")) {
|
||||
delegate.start(listener);
|
||||
succeeded = true;
|
||||
resolving = false;
|
||||
return;
|
||||
}
|
||||
if (executor == null) {
|
||||
executor = SharedResourceHolder.get(executorResource);
|
||||
}
|
||||
|
||||
class Resolve implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
String zone;
|
||||
boolean supportIpv6;
|
||||
ImmutableMap<String, ?> rawBootstrap = null;
|
||||
try {
|
||||
zone = queryZoneMetadata(METADATA_URL_ZONE);
|
||||
supportIpv6 = queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6);
|
||||
rawBootstrap = generateBootstrap(zone, supportIpv6);
|
||||
} catch (IOException e) {
|
||||
listener.onError(Status.INTERNAL.withDescription("Unable to get metadata").withCause(e));
|
||||
} finally {
|
||||
final ImmutableMap<String, ?> finalRawBootstrap = rawBootstrap;
|
||||
syncContext.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!shutdown && finalRawBootstrap != null) {
|
||||
xdsClientPoolFactory.setBootstrapOverride(finalRawBootstrap);
|
||||
delegate.start(listener);
|
||||
succeeded = true;
|
||||
}
|
||||
resolving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executor.execute(new Resolve());
|
||||
}
|
||||
|
||||
private static ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
|
||||
ImmutableMap.Builder<String, Object> nodeBuilder = ImmutableMap.builder();
|
||||
nodeBuilder.put("id", "C2P");
|
||||
if (!zone.isEmpty()) {
|
||||
nodeBuilder.put("locality", ImmutableMap.of("zone", zone));
|
||||
}
|
||||
if (supportIpv6) {
|
||||
nodeBuilder.put("metadata",
|
||||
ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
|
||||
}
|
||||
ImmutableMap.Builder<String, Object> serverBuilder = ImmutableMap.builder();
|
||||
serverBuilder.put("server_uri", "directpath-trafficdirector.googleapis.com");
|
||||
serverBuilder.put("channel_creds",
|
||||
ImmutableList.of(ImmutableMap.of("type", "google_default")));
|
||||
return ImmutableMap.of(
|
||||
"node", nodeBuilder.build(),
|
||||
"xds_servers", ImmutableList.of(serverBuilder.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
if (succeeded) {
|
||||
delegate.refresh();
|
||||
} else if (!resolving) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
shutdown = true;
|
||||
if (delegate != null) {
|
||||
delegate.shutdown();
|
||||
}
|
||||
if (executor != null && usingExecutorResource) {
|
||||
executor = SharedResourceHolder.release(executorResource, executor);
|
||||
}
|
||||
}
|
||||
|
||||
private String queryZoneMetadata(String url) throws IOException {
|
||||
HttpURLConnection con = null;
|
||||
String respBody;
|
||||
try {
|
||||
con = httpConnectionProvider.createConnection(url);
|
||||
if (con.getResponseCode() != 200) {
|
||||
return "";
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(con.getInputStream(), Charsets.UTF_8)) {
|
||||
respBody = CharStreams.toString(reader);
|
||||
}
|
||||
} finally {
|
||||
if (con != null) {
|
||||
con.disconnect();
|
||||
}
|
||||
}
|
||||
int index = respBody.lastIndexOf('/');
|
||||
return index == -1 ? "" : respBody.substring(index + 1);
|
||||
}
|
||||
|
||||
private boolean queryIpv6SupportMetadata(String url) throws IOException {
|
||||
HttpURLConnection con = null;
|
||||
try {
|
||||
con = httpConnectionProvider.createConnection(url);
|
||||
return con.getResponseCode() == 200;
|
||||
} finally {
|
||||
if (con != null) {
|
||||
con.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setHttpConnectionProvider(HttpConnectionProvider httpConnectionProvider) {
|
||||
this.httpConnectionProvider = httpConnectionProvider;
|
||||
}
|
||||
|
||||
private static URI overrideUriScheme(URI uri, String scheme) {
|
||||
URI res;
|
||||
try {
|
||||
res = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment());
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new IllegalArgumentException("Invalid scheme: " + scheme, ex);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private enum HttpConnectionFactory implements HttpConnectionProvider {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public HttpURLConnection createConnection(String url) throws IOException {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
|
||||
con.setRequestMethod("GET");
|
||||
con.setReadTimeout(10000);
|
||||
con.setRequestProperty("Metadata-Flavor", "Google");
|
||||
return con;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
interface HttpConnectionProvider {
|
||||
HttpURLConnection createConnection(String url) throws IOException;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2021 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 io.grpc.Internal;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.Args;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A provider for {@link GoogleCloudToProdNameResolver}.
|
||||
*/
|
||||
@Internal
|
||||
public final class GoogleCloudToProdNameResolverProvider extends NameResolverProvider {
|
||||
|
||||
private static final String SCHEME = "google-c2p";
|
||||
|
||||
@Override
|
||||
public NameResolver newNameResolver(URI targetUri, Args args) {
|
||||
if (SCHEME.equals(targetUri.getScheme())) {
|
||||
return new GoogleCloudToProdNameResolver(
|
||||
targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR,
|
||||
SharedXdsClientPoolProvider.getDefaultProvider());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultScheme() {
|
||||
return SCHEME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int priority() {
|
||||
return 4;
|
||||
}
|
||||
}
|
|
@ -30,8 +30,10 @@ import io.grpc.xds.Bootstrapper.BootstrapInfo;
|
|||
import io.grpc.xds.Bootstrapper.ServerInfo;
|
||||
import io.grpc.xds.EnvoyProtoData.Node;
|
||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
@ -45,6 +47,7 @@ final class SharedXdsClientPoolProvider implements XdsClientPoolFactory {
|
|||
|
||||
private final Bootstrapper bootstrapper;
|
||||
private final Object lock = new Object();
|
||||
private final AtomicReference<Map<String, ?>> bootstrapOverride = new AtomicReference<>();
|
||||
private volatile ObjectPool<XdsClient> xdsClientPool;
|
||||
|
||||
private SharedXdsClientPoolProvider() {
|
||||
|
@ -60,6 +63,11 @@ final class SharedXdsClientPoolProvider implements XdsClientPoolFactory {
|
|||
return SharedXdsClientPoolProviderHolder.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBootstrapOverride(Map<String, ?> bootstrap) {
|
||||
bootstrapOverride.set(bootstrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPool<XdsClient> getXdsClientPool() throws XdsInitializationException {
|
||||
ObjectPool<XdsClient> ref = xdsClientPool;
|
||||
|
@ -67,7 +75,13 @@ final class SharedXdsClientPoolProvider implements XdsClientPoolFactory {
|
|||
synchronized (lock) {
|
||||
ref = xdsClientPool;
|
||||
if (ref == null) {
|
||||
BootstrapInfo bootstrapInfo = bootstrapper.bootstrap();
|
||||
BootstrapInfo bootstrapInfo;
|
||||
Map<String, ?> rawBootstrap = bootstrapOverride.get();
|
||||
if (rawBootstrap != null) {
|
||||
bootstrapInfo = bootstrapper.bootstrap(rawBootstrap);
|
||||
} else {
|
||||
bootstrapInfo = bootstrapper.bootstrap();
|
||||
}
|
||||
if (bootstrapInfo.getServers().isEmpty()) {
|
||||
throw new XdsInitializationException("No xDS server provided");
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import io.grpc.NameResolver.Args;
|
|||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -76,6 +77,8 @@ public final class XdsNameResolverProvider extends NameResolverProvider {
|
|||
}
|
||||
|
||||
interface XdsClientPoolFactory {
|
||||
void setBootstrapOverride(Map<String, ?> bootstrap);
|
||||
|
||||
ObjectPool<XdsClient> getXdsClientPool() throws XdsInitializationException;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
io.grpc.xds.XdsNameResolverProvider
|
||||
io.grpc.xds.GoogleCloudToProdNameResolverProvider
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2021 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.mockito.Mockito.mock;
|
||||
|
||||
import io.grpc.ChannelLogger;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.internal.FakeClock;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import java.net.URI;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GoogleCloudToProdNameResolverProvider}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class GoogleCloudToProdNameResolverProviderTest {
|
||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
||||
new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
|
||||
private final FakeClock fakeClock = new FakeClock();
|
||||
private final NameResolver.Args args = NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(8080)
|
||||
.setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR)
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class))
|
||||
.setScheduledExecutorService(fakeClock.getScheduledExecutorService())
|
||||
.setChannelLogger(mock(ChannelLogger.class))
|
||||
.build();
|
||||
|
||||
private final NameResolverRegistry nsRegistry = NameResolverRegistry.getDefaultRegistry();
|
||||
|
||||
@Test
|
||||
public void provided() {
|
||||
NameResolver resolver = nsRegistry.asFactory().newNameResolver(
|
||||
URI.create("google-c2p:///foo.googleapis.com"), args);
|
||||
assertThat(resolver).isInstanceOf(GoogleCloudToProdNameResolver.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright 2021 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.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import io.grpc.ChannelLogger;
|
||||
import io.grpc.NameResolver;
|
||||
import io.grpc.NameResolver.Args;
|
||||
import io.grpc.NameResolver.ServiceConfigParser;
|
||||
import io.grpc.NameResolverProvider;
|
||||
import io.grpc.NameResolverRegistry;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.Status.Code;
|
||||
import io.grpc.SynchronizationContext;
|
||||
import io.grpc.internal.FakeClock;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.ObjectPool;
|
||||
import io.grpc.internal.SharedResourceHolder.Resource;
|
||||
import io.grpc.xds.GoogleCloudToProdNameResolver.HttpConnectionProvider;
|
||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class GoogleCloudToProdNameResolverTest {
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
private static final URI TARGET_URI = URI.create("google-c2p:///googleapis.com");
|
||||
private static final String ZONE = "us-central1-a";
|
||||
private static final int DEFAULT_PORT = 887;
|
||||
|
||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
||||
new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
private final NameResolver.Args args = NameResolver.Args.newBuilder()
|
||||
.setDefaultPort(DEFAULT_PORT)
|
||||
.setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR)
|
||||
.setSynchronizationContext(syncContext)
|
||||
.setServiceConfigParser(mock(ServiceConfigParser.class))
|
||||
.setChannelLogger(mock(ChannelLogger.class))
|
||||
.build();
|
||||
private final FakeClock fakeExecutor = new FakeClock();
|
||||
private final FakeXdsClientPoolFactory fakeXdsClientPoolFactory = new FakeXdsClientPoolFactory();
|
||||
private final Resource<Executor> fakeExecutorResource = new Resource<Executor>() {
|
||||
@Override
|
||||
public Executor create() {
|
||||
return fakeExecutor.getScheduledExecutorService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(Executor instance) {}
|
||||
};
|
||||
|
||||
private final NameResolverRegistry nsRegistry = new NameResolverRegistry();
|
||||
private final Map<String, NameResolver> delegatedResolver = new HashMap<>();
|
||||
|
||||
@Mock
|
||||
private NameResolver.Listener2 mockListener;
|
||||
@Captor
|
||||
private ArgumentCaptor<Status> errorCaptor;
|
||||
private boolean originalIsOnGcp;
|
||||
private boolean originalXdsBootstrapProvided;
|
||||
private GoogleCloudToProdNameResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
nsRegistry.register(new FakeNsProvider("dns"));
|
||||
nsRegistry.register(new FakeNsProvider("xds"));
|
||||
originalIsOnGcp = GoogleCloudToProdNameResolver.isOnGcp;
|
||||
originalXdsBootstrapProvided = GoogleCloudToProdNameResolver.xdsBootstrapProvided;
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
GoogleCloudToProdNameResolver.isOnGcp = originalIsOnGcp;
|
||||
GoogleCloudToProdNameResolver.xdsBootstrapProvided = originalXdsBootstrapProvided;
|
||||
resolver.shutdown();
|
||||
verify(Iterables.getOnlyElement(delegatedResolver.values())).shutdown();
|
||||
}
|
||||
|
||||
private void createResolver() {
|
||||
HttpConnectionProvider httpConnections = new HttpConnectionProvider() {
|
||||
@Override
|
||||
public HttpURLConnection createConnection(String url) throws IOException {
|
||||
HttpURLConnection con = mock(HttpURLConnection.class);
|
||||
when(con.getResponseCode()).thenReturn(200);
|
||||
if (url.equals(GoogleCloudToProdNameResolver.METADATA_URL_ZONE)) {
|
||||
when(con.getInputStream()).thenReturn(
|
||||
new ByteArrayInputStream(("/" + ZONE).getBytes(StandardCharsets.UTF_8)));
|
||||
return con;
|
||||
} else if (url.equals(GoogleCloudToProdNameResolver.METADATA_URL_SUPPORT_IPV6)) {
|
||||
return con;
|
||||
}
|
||||
throw new AssertionError("Unknown http query");
|
||||
}
|
||||
};
|
||||
resolver = new GoogleCloudToProdNameResolver(
|
||||
TARGET_URI, args, fakeExecutorResource, fakeXdsClientPoolFactory, nsRegistry.asFactory());
|
||||
resolver.setHttpConnectionProvider(httpConnections);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notOnGcpDelegateToDns() {
|
||||
GoogleCloudToProdNameResolver.isOnGcp = false;
|
||||
createResolver();
|
||||
resolver.start(mockListener);
|
||||
assertThat(delegatedResolver.keySet()).containsExactly("dns");
|
||||
verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasProvidedBootstrapDelegateToDns() {
|
||||
GoogleCloudToProdNameResolver.isOnGcp = true;
|
||||
GoogleCloudToProdNameResolver.xdsBootstrapProvided = true;
|
||||
createResolver();
|
||||
resolver.start(mockListener);
|
||||
assertThat(delegatedResolver.keySet()).containsExactly("dns");
|
||||
verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void onGcpAndNoProvidedBootstrapDelegateToXds() {
|
||||
GoogleCloudToProdNameResolver.isOnGcp = true;
|
||||
GoogleCloudToProdNameResolver.xdsBootstrapProvided = false;
|
||||
createResolver();
|
||||
resolver.start(mockListener);
|
||||
fakeExecutor.runDueTasks();
|
||||
assertThat(delegatedResolver.keySet()).containsExactly("xds");
|
||||
verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
|
||||
Map<String, ?> bootstrap = fakeXdsClientPoolFactory.bootstrapRef.get();
|
||||
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
|
||||
assertThat(node).containsExactly(
|
||||
"id", "C2P", "locality", ImmutableMap.of("zone", ZONE),
|
||||
"metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
|
||||
Map<String, ?> server = Iterables.getOnlyElement(
|
||||
(List<Map<String, ?>>) bootstrap.get("xds_servers"));
|
||||
assertThat(server).containsExactly(
|
||||
"server_uri", "directpath-trafficdirector.googleapis.com",
|
||||
"channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failToQueryMetadata() {
|
||||
GoogleCloudToProdNameResolver.isOnGcp = true;
|
||||
GoogleCloudToProdNameResolver.xdsBootstrapProvided = false;
|
||||
createResolver();
|
||||
HttpConnectionProvider httpConnections = new HttpConnectionProvider() {
|
||||
@Override
|
||||
public HttpURLConnection createConnection(String url) throws IOException {
|
||||
HttpURLConnection con = mock(HttpURLConnection.class);
|
||||
when(con.getResponseCode()).thenThrow(new IOException("unknown error"));
|
||||
return con;
|
||||
}
|
||||
};
|
||||
resolver.setHttpConnectionProvider(httpConnections);
|
||||
resolver.start(mockListener);
|
||||
fakeExecutor.runDueTasks();
|
||||
verify(mockListener).onError(errorCaptor.capture());
|
||||
assertThat(errorCaptor.getValue().getCode()).isEqualTo(Code.INTERNAL);
|
||||
assertThat(errorCaptor.getValue().getDescription()).isEqualTo("Unable to get metadata");
|
||||
}
|
||||
|
||||
private final class FakeNsProvider extends NameResolverProvider {
|
||||
private final String scheme;
|
||||
|
||||
private FakeNsProvider(String scheme) {
|
||||
this.scheme = scheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameResolver newNameResolver(URI targetUri, Args args) {
|
||||
if (scheme.equals(targetUri.getScheme())) {
|
||||
NameResolver resolver = mock(NameResolver.class);
|
||||
delegatedResolver.put(scheme, resolver);
|
||||
return resolver;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int priority() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultScheme() {
|
||||
return scheme;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
|
||||
private final AtomicReference<Map<String, ?>> bootstrapRef = new AtomicReference<>();
|
||||
|
||||
@Override
|
||||
public void setBootstrapOverride(Map<String, ?> bootstrap) {
|
||||
bootstrapRef.set(bootstrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPool<XdsClient> getXdsClientPool() {
|
||||
throw new UnsupportedOperationException("Should not be called");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -159,6 +159,11 @@ public class XdsNameResolverTest {
|
|||
@Test
|
||||
public void resolving_failToCreateXdsClientPool() {
|
||||
XdsClientPoolFactory xdsClientPoolFactory = new XdsClientPoolFactory() {
|
||||
@Override
|
||||
public void setBootstrapOverride(Map<String, ?> bootstrap) {
|
||||
throw new UnsupportedOperationException("Should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPool<XdsClient> getXdsClientPool() throws XdsInitializationException {
|
||||
throw new XdsInitializationException("Fail to read bootstrap file");
|
||||
|
@ -1394,6 +1399,11 @@ public class XdsNameResolverTest {
|
|||
|
||||
private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
|
||||
|
||||
@Override
|
||||
public void setBootstrapOverride(Map<String, ?> bootstrap) {
|
||||
throw new UnsupportedOperationException("Should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectPool<XdsClient> getXdsClientPool() throws XdsInitializationException {
|
||||
return new ObjectPool<XdsClient>() {
|
||||
|
|
Loading…
Reference in New Issue