Skip to content

Commit

Permalink
xds: throw XdsInitializationException when reading bootstrap file enc…
Browse files Browse the repository at this point in the history
…ounters error (grpc#7420)

Introduce XdsInitializationException, which is thrown when gRPC fails to read the xDS bootstrap information, or fails to create the XdsClient object with loaded bootstrap configurations. gRPC components (e.g., the XdsNameResolver) is expected to propagate such exceptions gracefully to the channel.
  • Loading branch information
voidzcy committed Sep 14, 2020
1 parent af6fbf6 commit 9dd56a7
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 101 deletions.
43 changes: 27 additions & 16 deletions xds/src/main/java/io/grpc/xds/Bootstrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,22 @@ public abstract class Bootstrapper {

private static final Bootstrapper DEFAULT_INSTANCE = new Bootstrapper() {
@Override
public BootstrapInfo readBootstrap() throws IOException {
public BootstrapInfo readBootstrap() throws XdsInitializationException {
String filePath = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR);
if (filePath == null) {
throw
new IOException("Environment variable " + BOOTSTRAP_PATH_SYS_ENV_VAR + " not defined.");
throw new XdsInitializationException(
"Environment variable " + BOOTSTRAP_PATH_SYS_ENV_VAR + " not defined.");
}
XdsLogger
.withPrefix(LOG_PREFIX)
.log(XdsLogLevel.INFO, BOOTSTRAP_PATH_SYS_ENV_VAR + "={0}", filePath);
return parseConfig(
new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8));
String fileContent;
try {
fileContent = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new XdsInitializationException("Fail to read bootstrap file", e);
}
return parseConfig(fileContent);
}
};

Expand All @@ -74,42 +79,47 @@ public static Bootstrapper getInstance() {
/**
* Returns configurations from bootstrap.
*/
public abstract BootstrapInfo readBootstrap() throws IOException;
public abstract BootstrapInfo readBootstrap() throws XdsInitializationException;

/** Parses a raw string into {@link BootstrapInfo}. */
@VisibleForTesting
@SuppressWarnings("deprecation")
public static BootstrapInfo parseConfig(String rawData) throws IOException {
@SuppressWarnings("unchecked")
public static BootstrapInfo parseConfig(String rawData) throws XdsInitializationException {
XdsLogger logger = XdsLogger.withPrefix(LOG_PREFIX);
logger.log(XdsLogLevel.INFO, "Reading bootstrap information");
@SuppressWarnings("unchecked")
Map<String, ?> rawBootstrap = (Map<String, ?>) JsonParser.parse(rawData);
Map<String, ?> rawBootstrap;
try {
rawBootstrap = (Map<String, ?>) JsonParser.parse(rawData);
} catch (IOException e) {
throw new XdsInitializationException("Failed to parse JSON", e);
}
logger.log(XdsLogLevel.DEBUG, "Bootstrap configuration:\n{0}", rawBootstrap);

List<ServerInfo> servers = new ArrayList<>();
List<?> rawServerConfigs = JsonUtil.getList(rawBootstrap, "xds_servers");
if (rawServerConfigs == null) {
throw new IOException("Invalid bootstrap: 'xds_servers' does not exist.");
throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
}
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
for (Map<String, ?> serverConfig : serverConfigList) {
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
if (serverUri == null) {
throw new IOException("Invalid bootstrap: 'xds_servers' contains unknown server.");
throw new XdsInitializationException(
"Invalid bootstrap: missing 'xds_servers'");
}
logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
List<ChannelCreds> channelCredsOptions = new ArrayList<>();
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
throw new IOException(
throw new XdsInitializationException(
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
}
List<Map<String, ?>> channelCredsList = JsonUtil.checkObjectList(rawChannelCredsList);
for (Map<String, ?> channelCreds : channelCredsList) {
String type = JsonUtil.getString(channelCreds, "type");
if (type == null) {
throw new IOException(
throw new XdsInitializationException(
"Invalid bootstrap: server " + serverUri + " with 'channel_creds' type unspecified");
}
logger.log(XdsLogLevel.INFO, "Channel credentials option: {0}", type);
Expand Down Expand Up @@ -182,9 +192,10 @@ public static BootstrapInfo parseConfig(String rawData) throws IOException {
return new BootstrapInfo(servers, nodeBuilder.build(), certProviders);
}

static <T> T checkForNull(T value, String fieldName) throws IOException {
static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
if (value == null) {
throw new IOException("Invalid bootstrap: '" + fieldName + "' does not exist.");
throw new XdsInitializationException(
"Invalid bootstrap: '" + fieldName + "' does not exist.");
}
return value;
}
Expand Down
18 changes: 3 additions & 15 deletions xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,6 @@ public final class XdsClientWrapperForServerSds {
private XdsClient.ListenerWatcher listenerWatcher;
@VisibleForTesting final Set<ServerWatcher> serverWatchers = new HashSet<>();

/**
* Thrown when no suitable management server was found in the bootstrap file.
*/
public static final class ManagementServerNotFoundException extends IOException {

private static final long serialVersionUID = 1;

public ManagementServerNotFoundException(String msg) {
super(msg);
}
}

/**
* Creates a {@link XdsClientWrapperForServerSds}.
*
Expand Down Expand Up @@ -137,11 +125,11 @@ public void createXdsClientAndStart() throws IOException {
bootstrapInfo = Bootstrapper.getInstance().readBootstrap();
serverList = bootstrapInfo.getServers();
if (serverList.isEmpty()) {
throw new ManagementServerNotFoundException("No management server provided by bootstrap");
throw new XdsInitializationException("No management server provided by bootstrap");
}
} catch (IOException e) {
} catch (XdsInitializationException e) {
reportError(Status.fromThrowable(e));
throw e;
throw new IOException(e);
}
Node node = bootstrapInfo.getNode();
timeService = SharedResourceHolder.get(timeServiceResource);
Expand Down
32 changes: 32 additions & 0 deletions xds/src/main/java/io/grpc/xds/XdsInitializationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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;

/**
* Throws when fail to bootstrap or initialize the XdsClient.
*/
public final class XdsInitializationException extends Exception {
private static final long serialVersionUID = 1L;

public XdsInitializationException(String message) {
super(message);
}

public XdsInitializationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.xds.Bootstrapper;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.XdsInitializationException;
import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider;
import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory;
import java.io.IOException;
import java.util.concurrent.Executors;

/** Factory to create client-side SslContextProvider from UpstreamTlsContext. */
Expand Down Expand Up @@ -64,8 +64,8 @@ public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) {
.setDaemon(true)
.build()),
/* channelExecutor= */ null);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} catch (XdsInitializationException e) {
throw new RuntimeException(e);
}
} else if (CommonTlsContextUtil.hasCertProviderInstance(
upstreamTlsContext.getCommonTlsContext())) {
Expand All @@ -74,8 +74,8 @@ public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) {
upstreamTlsContext,
bootstrapper.readBootstrap().getNode().toEnvoyProtoNode(),
bootstrapper.readBootstrap().getCertProviders());
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} catch (XdsInitializationException e) {
throw new RuntimeException(e);
}
}
throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.xds.Bootstrapper;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
import io.grpc.xds.XdsInitializationException;
import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider;
import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory;
import java.io.IOException;
import java.util.concurrent.Executors;

/** Factory to create server-side SslContextProvider from DownstreamTlsContext. */
Expand Down Expand Up @@ -66,8 +66,8 @@ public SslContextProvider create(
.setDaemon(true)
.build()),
/* channelExecutor= */ null);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} catch (XdsInitializationException e) {
throw new RuntimeException(e);
}
} else if (CommonTlsContextUtil.hasCertProviderInstance(
downstreamTlsContext.getCommonTlsContext())) {
Expand All @@ -76,8 +76,8 @@ public SslContextProvider create(
downstreamTlsContext,
bootstrapper.readBootstrap().getNode().toEnvoyProtoNode(),
bootstrapper.readBootstrap().getCertProviders());
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} catch (XdsInitializationException e) {
throw new RuntimeException(e);
}
}
throw new UnsupportedOperationException("Unsupported configurations in DownstreamTlsContext!");
Expand Down
39 changes: 19 additions & 20 deletions xds/src/test/java/io/grpc/xds/BootstrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
Expand All @@ -44,7 +43,7 @@ public class BootstrapperTest {
@Rule public ExpectedException thrown = ExpectedException.none();

@Test
public void parseBootstrap_validData_singleXdsServer() throws IOException {
public void parseBootstrap_validData_singleXdsServer() throws XdsInitializationException {
String rawData = "{\n"
+ " \"node\": {\n"
+ " \"id\": \"ENVOY_NODE_ID\",\n"
Expand Down Expand Up @@ -95,7 +94,7 @@ public void parseBootstrap_validData_singleXdsServer() throws IOException {
}

@Test
public void parseBootstrap_validData_multipleXdsServers() throws IOException {
public void parseBootstrap_validData_multipleXdsServers() throws XdsInitializationException {
String rawData = "{\n"
+ " \"node\": {\n"
+ " \"id\": \"ENVOY_NODE_ID\",\n"
Expand Down Expand Up @@ -163,7 +162,7 @@ public void parseBootstrap_validData_multipleXdsServers() throws IOException {
}

@Test
public void parseBootstrap_IgnoreIrrelevantFields() throws IOException {
public void parseBootstrap_IgnoreIrrelevantFields() throws XdsInitializationException {
String rawData = "{\n"
+ " \"node\": {\n"
+ " \"id\": \"ENVOY_NODE_ID\",\n"
Expand Down Expand Up @@ -216,15 +215,15 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws IOException {
}

@Test
public void parseBootstrap_emptyData() throws IOException {
public void parseBootstrap_emptyData() throws XdsInitializationException {
String rawData = "";

thrown.expect(IOException.class);
thrown.expect(XdsInitializationException.class);
Bootstrapper.parseConfig(rawData);
}

@Test
public void parseBootstrap_minimumRequiredFields() throws IOException {
public void parseBootstrap_minimumRequiredFields() throws XdsInitializationException {
String rawData = "{\n"
+ " \"xds_servers\": []\n"
+ "}";
Expand All @@ -235,7 +234,7 @@ public void parseBootstrap_minimumRequiredFields() throws IOException {
}

@Test
public void parseBootstrap_minimalUsableData() throws IOException {
public void parseBootstrap_minimalUsableData() throws XdsInitializationException {
String rawData = "{\n"
+ " \"xds_servers\": [\n"
+ " {\n"
Expand All @@ -259,7 +258,7 @@ public void parseBootstrap_minimalUsableData() throws IOException {
}

@Test
public void parseBootstrap_noXdsServers() throws IOException {
public void parseBootstrap_noXdsServers() throws XdsInitializationException {
String rawData = "{\n"
+ " \"node\": {\n"
+ " \"id\": \"ENVOY_NODE_ID\",\n"
Expand All @@ -276,13 +275,13 @@ public void parseBootstrap_noXdsServers() throws IOException {
+ " }\n"
+ "}";

thrown.expect(IOException.class);
thrown.expect(XdsInitializationException.class);
thrown.expectMessage("Invalid bootstrap: 'xds_servers' does not exist.");
Bootstrapper.parseConfig(rawData);
}

@Test
public void parseBootstrap_serverWithoutServerUri() throws IOException {
public void parseBootstrap_serverWithoutServerUri() throws XdsInitializationException {
String rawData = "{"
+ " \"node\": {\n"
+ " \"id\": \"ENVOY_NODE_ID\",\n"
Expand All @@ -306,13 +305,13 @@ public void parseBootstrap_serverWithoutServerUri() throws IOException {
+ " ]\n "
+ "}";

thrown.expect(IOException.class);
thrown.expectMessage("Invalid bootstrap: 'xds_servers' contains unknown server.");
thrown.expect(XdsInitializationException.class);
thrown.expectMessage("Invalid bootstrap: missing 'xds_servers'");
Bootstrapper.parseConfig(rawData);
}

@Test
public void parseBootstrap_validData_certProviderInstances() throws IOException {
public void parseBootstrap_validData_certProviderInstances() throws XdsInitializationException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
Expand Down Expand Up @@ -366,7 +365,7 @@ public void parseBootstrap_validData_certProviderInstances() throws IOException
}

@Test
public void parseBootstrap_badPluginName() throws IOException {
public void parseBootstrap_badPluginName() throws XdsInitializationException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
Expand Down Expand Up @@ -413,7 +412,7 @@ public void parseBootstrap_badPluginName() throws IOException {
}

@Test
public void parseBootstrap_badConfig() throws IOException {
public void parseBootstrap_badConfig() throws XdsInitializationException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
Expand All @@ -438,7 +437,7 @@ public void parseBootstrap_badConfig() throws IOException {
}

@Test
public void parseBootstrap_missingConfig() throws IOException {
public void parseBootstrap_missingConfig() {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
Expand All @@ -456,15 +455,15 @@ public void parseBootstrap_missingConfig() throws IOException {
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (IOException expected) {
} catch (XdsInitializationException expected) {
assertThat(expected)
.hasMessageThat()
.isEqualTo("Invalid bootstrap: 'config' does not exist.");
}
}

@Test
public void parseBootstrap_missingPluginName() throws IOException {
public void parseBootstrap_missingPluginName() {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
Expand Down Expand Up @@ -504,7 +503,7 @@ public void parseBootstrap_missingPluginName() throws IOException {
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (IOException expected) {
} catch (XdsInitializationException expected) {
assertThat(expected)
.hasMessageThat()
.isEqualTo("Invalid bootstrap: 'plugin_name' does not exist.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public void startXdsClient_expectException() {
verify(mockServerWatcher).onError(argCaptor.capture());
Status captured = argCaptor.getValue();
assertThat(captured.getCode()).isEqualTo(Status.Code.UNKNOWN);
assertThat(captured.getCause()).isInstanceOf(IOException.class);
assertThat(captured.getCause()).isInstanceOf(XdsInitializationException.class);
assertThat(captured.getCause())
.hasMessageThat()
.contains("Environment variable GRPC_XDS_BOOTSTRAP not defined");
Expand Down

0 comments on commit 9dd56a7

Please sign in to comment.