From d168632f8229e387c19e820c26cf1d0f25bb0c84 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Thu, 5 Dec 2019 16:45:28 -0800 Subject: [PATCH] xds: support multiple xDS servers in bootstrap file (#6493) Support bootstrap file containing multiple xDS servers, with each has its own server URI and channel credential options. Multiple xDS servers are provided in case of one not reachable. For now, we would only use the first one. This change also formats JSON strings in bootstrap related tests and add several tests for parsing bootstrap JSON as completeness. Implementation of XdsClient is changed to take in a list of xDS servers. But still, we only use the first one. --- .../main/java/io/grpc/xds/Bootstrapper.java | 91 +++--- .../main/java/io/grpc/xds/LookasideLb.java | 17 +- .../main/java/io/grpc/xds/XdsClientImpl.java | 20 +- .../java/io/grpc/xds/XdsNameResolver.java | 30 +- .../java/io/grpc/xds/BootstrapperTest.java | 283 ++++++++++++++---- .../java/io/grpc/xds/LookasideLbTest.java | 8 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 25 +- 7 files changed, 353 insertions(+), 121 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index 2ab77f482f..ab6076f833 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -73,28 +73,33 @@ public abstract class Bootstrapper { @SuppressWarnings("unchecked") Map rawBootstrap = (Map) JsonParser.parse(rawData); - Map rawServerConfig = JsonUtil.getObject(rawBootstrap, "xds_server"); - if (rawServerConfig == null) { - throw new IOException("Invalid bootstrap: 'xds_server' does not exist."); + List servers = new ArrayList<>(); + List rawServerConfigs = JsonUtil.getList(rawBootstrap, "xds_servers"); + if (rawServerConfigs == null) { + throw new IOException("Invalid bootstrap: 'xds_servers' does not exist."); } - // Field "server_uri" is required. - String serverUri = JsonUtil.getString(rawServerConfig, "server_uri"); - if (serverUri == null) { - throw new IOException("Invalid bootstrap: 'xds_server : server_uri' does not exist."); - } - List channelCredsOptions = new ArrayList<>(); - List rawChannelCredsList = JsonUtil.getList(rawServerConfig, "channel_creds"); - // List of channel creds is optional. - if (rawChannelCredsList != null) { - List> channelCredsList = JsonUtil.checkObjectList(rawChannelCredsList); - for (Map channelCreds : channelCredsList) { - String type = JsonUtil.getString(channelCreds, "type"); - if (type == null) { - throw new IOException("Invalid bootstrap: 'channel_creds' contains unknown type."); - } - ChannelCreds creds = new ChannelCreds(type, JsonUtil.getObject(channelCreds, "config")); - channelCredsOptions.add(creds); + List> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs); + for (Map serverConfig : serverConfigList) { + String serverUri = JsonUtil.getString(serverConfig, "server_uri"); + if (serverUri == null) { + throw new IOException("Invalid bootstrap: 'xds_servers' contains unknown server."); } + List channelCredsOptions = new ArrayList<>(); + List rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds"); + // List of channel creds is optional. + if (rawChannelCredsList != null) { + List> channelCredsList = JsonUtil.checkObjectList(rawChannelCredsList); + for (Map channelCreds : channelCredsList) { + String type = JsonUtil.getString(channelCreds, "type"); + if (type == null) { + throw new IOException("Invalid bootstrap: 'xds_servers' contains server with " + + "unknown type 'channel_creds'."); + } + ChannelCreds creds = new ChannelCreds(type, JsonUtil.getObject(channelCreds, "config")); + channelCredsOptions.add(creds); + } + } + servers.add(new ServerInfo(serverUri, channelCredsOptions)); } Node.Builder nodeBuilder = Node.newBuilder(); @@ -133,7 +138,7 @@ public abstract class Bootstrapper { } nodeBuilder.setBuildVersion(GrpcUtil.getGrpcBuildVersion()); - return new BootstrapInfo(serverUri, channelCredsOptions, nodeBuilder.build()); + return new BootstrapInfo(servers, nodeBuilder.build()); } /** @@ -202,27 +207,49 @@ public abstract class Bootstrapper { } } + /** + * Data class containing xDS server information, such as server URI and channel credential + * options to be used for communication. + */ + @Immutable + static class ServerInfo { + private final String serverUri; + private final List channelCredsList; + + @VisibleForTesting + ServerInfo(String serverUri, List channelCredsList) { + this.serverUri = serverUri; + this.channelCredsList = channelCredsList; + } + + String getServerUri() { + return serverUri; + } + + List getChannelCredentials() { + return Collections.unmodifiableList(channelCredsList); + } + } + /** * Data class containing the results of reading bootstrap. */ @Immutable public static class BootstrapInfo { - private final String serverUri; - private final List channelCredsList; + private List servers; private final Node node; @VisibleForTesting - BootstrapInfo(String serverUri, List channelCredsList, Node node) { - this.serverUri = serverUri; - this.channelCredsList = channelCredsList; + BootstrapInfo(List servers, Node node) { + this.servers = servers; this.node = node; } /** - * Returns the URI the traffic director to be connected to. + * Returns the list of xDS servers to be connected to. */ - String getServerUri() { - return serverUri; + List getServers() { + return Collections.unmodifiableList(servers); } /** @@ -232,11 +259,5 @@ public abstract class Bootstrapper { return node; } - /** - * Returns the credentials to use when communicating with the xDS server. - */ - List getChannelCredentials() { - return Collections.unmodifiableList(channelCredsList); - } } } diff --git a/xds/src/main/java/io/grpc/xds/LookasideLb.java b/xds/src/main/java/io/grpc/xds/LookasideLb.java index a3af011d00..aae38c529f 100644 --- a/xds/src/main/java/io/grpc/xds/LookasideLb.java +++ b/xds/src/main/java/io/grpc/xds/LookasideLb.java @@ -41,6 +41,7 @@ import io.grpc.internal.ObjectPool; import io.grpc.util.GracefulSwitchLoadBalancer; 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; @@ -204,9 +205,21 @@ final class LookasideLb extends LoadBalancer { new ErrorPicker(Status.UNAVAILABLE.withCause(e))); return; } + + List serverList = bootstrapInfo.getServers(); + if (serverList.isEmpty()) { + lookasideLbHelper.updateBalancingState( + TRANSIENT_FAILURE, + new ErrorPicker( + Status.UNAVAILABLE + .withDescription("No traffic director provided by bootstrap"))); + return; + } + // Currently we only support using the first server from bootstrap. + ServerInfo serverInfo = serverList.get(0); channel = initLbChannel( - lookasideLbHelper, bootstrapInfo.getServerUri(), - bootstrapInfo.getChannelCredentials()); + lookasideLbHelper, serverInfo.getServerUri(), + serverInfo.getChannelCredentials()); xdsClientRef = new RefCountedXdsClientObjectPool(new XdsClientFactory() { @Override XdsClient createXdsClient() { diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 37e81aec39..c18e92921a 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -49,6 +49,7 @@ import io.grpc.alts.GoogleDefaultChannelBuilder; import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; 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; @@ -135,19 +136,14 @@ final class XdsClientImpl extends XdsClient { private String ldsResourceName; XdsClientImpl( - // URI of the management server to be connected to. - String serverUri, + List servers, // list of management servers Node node, - // List of channel credential configurations for the channel to management server. - // Should pick the first supported one. - List channelCredsList, SynchronizationContext syncContext, ScheduledExecutorService timeService, BackoffPolicy.Provider backoffPolicyProvider, Stopwatch stopwatch) { this( - buildChannel(checkNotNull(serverUri, "serverUri"), - checkNotNull(channelCredsList, "channelCredsList")), + buildChannel(checkNotNull(servers, "servers")), node, syncContext, timeService, @@ -319,9 +315,15 @@ final class XdsClientImpl extends XdsClient { } /** - * Builds a channel to the given server URI with the first supported channel creds config. + * Builds a channel to one of the provided management servers. + * + *

Note: currently we only support using the first server. */ - private static ManagedChannel buildChannel(String serverUri,List channelCredsList) { + private static ManagedChannel buildChannel(List servers) { + checkArgument(!servers.isEmpty(), "No management server provided."); + ServerInfo serverInfo = servers.get(0); + String serverUri = serverInfo.getServerUri(); + List channelCredsList = serverInfo.getChannelCredentials(); ManagedChannel ch = null; // Use the first supported channel credentials configuration. // Currently, only "google_default" is supported. diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index f4c69cffaf..125d25c75d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -29,6 +29,7 @@ import io.grpc.internal.GrpcAttributes; import io.grpc.internal.JsonParser; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ChannelCreds; +import io.grpc.xds.Bootstrapper.ServerInfo; import java.io.IOException; import java.net.URI; import java.util.Collections; @@ -89,13 +90,26 @@ final class XdsNameResolver extends NameResolver { return; } - String serviceConfig = "{" - + "\"loadBalancingConfig\": [" - + "{\"xds_experimental\" : {" - + "\"balancerName\" : \"" + bootstrapInfo.getServerUri() + "\"," - + "\"childPolicy\" : [{\"round_robin\" : {}}]" - + "}}" - + "]}"; + List serverList = bootstrapInfo.getServers(); + if (serverList.isEmpty()) { + listener.onError( + Status.UNAVAILABLE.withDescription("No traffic director provided by bootstrap")); + return; + } + + // Currently we only support using the first server from bootstrap. + ServerInfo serverInfo = serverList.get(0); + + String serviceConfig = "{\n" + + " \"loadBalancingConfig\": [\n" + + " {\n" + + " \"xds_experimental\": {\n" + + " \"balancerName\": \"" + serverInfo.getServerUri() + "\",\n" + + " \"childPolicy\": [ {\"round_robin\": {} } ]\n" + + " }\n" + + " }" + + " ]\n" + + "}"; Map config; try { config = (Map) JsonParser.parse(serviceConfig); @@ -108,7 +122,7 @@ final class XdsNameResolver extends NameResolver { Attributes.newBuilder() .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, config) .set(XDS_NODE, bootstrapInfo.getNode()) - .set(XDS_CHANNEL_CREDS_LIST, bootstrapInfo.getChannelCredentials()) + .set(XDS_CHANNEL_CREDS_LIST, serverInfo.getChannelCredentials()) .build(); ResolutionResult result = ResolutionResult.newBuilder() diff --git a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java index e11aa06613..87c46dcc32 100644 --- a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java @@ -18,13 +18,16 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.Iterables; import com.google.protobuf.Struct; import com.google.protobuf.Value; import io.envoyproxy.envoy.api.v2.core.Locality; import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.internal.GrpcUtil; import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.Bootstrapper.ServerInfo; import java.io.IOException; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -38,35 +41,163 @@ public class BootstrapperTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test - public void parseBootstrap_validData() throws IOException { - String rawData = "{" - + "\"node\": {" - + "\"id\": \"ENVOY_NODE_ID\"," - + "\"cluster\": \"ENVOY_CLUSTER\"," - + "\"locality\": {" - + "\"region\": \"ENVOY_REGION\", \"zone\": \"ENVOY_ZONE\", \"sub_zone\": \"ENVOY_SUBZONE\"" - + "}," - + "\"metadata\": {" - + "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", " - + "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"" - + "}" - + "}," - + "\"xds_server\": {" - + "\"server_uri\": \"trafficdirector.googleapis.com:443\"," - + "\"channel_creds\": " - + "[ {\"type\": \"tls\"}, {\"type\": \"loas\"}, {\"type\": \"google_default\"} ]" - + "} " + public void parseBootstrap_validData_singleXdsServer() throws IOException { + String rawData = "{\n" + + " \"node\": {\n" + + " \"id\": \"ENVOY_NODE_ID\",\n" + + " \"cluster\": \"ENVOY_CLUSTER\",\n" + + " \"locality\": {\n" + + " \"region\": \"ENVOY_REGION\",\n" + + " \"zone\": \"ENVOY_ZONE\",\n" + + " \"sub_zone\": \"ENVOY_SUBZONE\"\n" + + " },\n" + + " \"metadata\": {\n" + + " \"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\",\n" + + " \"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"trafficdirector.googleapis.com:443\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"tls\"}, {\"type\": \"loas\"}, {\"type\": \"google_default\"}\n" + + " ]\n" + + " }\n" + + " ]\n" + "}"; BootstrapInfo info = Bootstrapper.parseConfig(rawData); - assertThat(info.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); - assertThat(info.getChannelCredentials()).hasSize(3); - assertThat(info.getChannelCredentials().get(0).getType()).isEqualTo("tls"); - assertThat(info.getChannelCredentials().get(0).getConfig()).isNull(); - assertThat(info.getChannelCredentials().get(1).getType()).isEqualTo("loas"); - assertThat(info.getChannelCredentials().get(1).getConfig()).isNull(); - assertThat(info.getChannelCredentials().get(2).getType()).isEqualTo("google_default"); - assertThat(info.getChannelCredentials().get(2).getConfig()).isNull(); + assertThat(info.getServers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.getServers()); + assertThat(serverInfo.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(serverInfo.getChannelCredentials()).hasSize(3); + assertThat(serverInfo.getChannelCredentials().get(0).getType()).isEqualTo("tls"); + assertThat(serverInfo.getChannelCredentials().get(0).getConfig()).isNull(); + assertThat(serverInfo.getChannelCredentials().get(1).getType()).isEqualTo("loas"); + assertThat(serverInfo.getChannelCredentials().get(1).getConfig()).isNull(); + assertThat(serverInfo.getChannelCredentials().get(2).getType()).isEqualTo("google_default"); + assertThat(serverInfo.getChannelCredentials().get(2).getConfig()).isNull(); + assertThat(info.getNode()).isEqualTo( + Node.newBuilder() + .setId("ENVOY_NODE_ID") + .setCluster("ENVOY_CLUSTER") + .setLocality( + Locality.newBuilder() + .setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE")) + .setMetadata( + Struct.newBuilder() + .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", + Value.newBuilder().setStringValue("ENVOY_PORT").build()) + .putFields("TRAFFICDIRECTOR_NETWORK_NAME", + Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()) + .build()) + .setBuildVersion(GrpcUtil.getGrpcBuildVersion()) + .build()); + } + + @Test + public void parseBootstrap_validData_multipleXdsServers() throws IOException { + String rawData = "{\n" + + " \"node\": {\n" + + " \"id\": \"ENVOY_NODE_ID\",\n" + + " \"cluster\": \"ENVOY_CLUSTER\",\n" + + " \"locality\": {\n" + + " \"region\": \"ENVOY_REGION\",\n" + + " \"zone\": \"ENVOY_ZONE\",\n" + + " \"sub_zone\": \"ENVOY_SUBZONE\"\n" + + " },\n" + + " \"metadata\": {\n" + + " \"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\",\n" + + " \"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"trafficdirector-foo.googleapis.com:443\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"tls\"}, {\"type\": \"loas\"}, {\"type\": \"google_default\"}\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"server_uri\": \"trafficdirector-bar.googleapis.com:443\",\n" + + " \"channel_creds\": []\n" + + " }\n" + + " ]\n" + + "}"; + + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.getServers()).hasSize(2); + List serverInfoList = info.getServers(); + assertThat(serverInfoList.get(0).getServerUri()) + .isEqualTo("trafficdirector-foo.googleapis.com:443"); + assertThat(serverInfoList.get(0).getChannelCredentials()).hasSize(3); + assertThat(serverInfoList.get(0).getChannelCredentials().get(0).getType()).isEqualTo("tls"); + assertThat(serverInfoList.get(0).getChannelCredentials().get(0).getConfig()).isNull(); + assertThat(serverInfoList.get(0).getChannelCredentials().get(1).getType()).isEqualTo("loas"); + assertThat(serverInfoList.get(0).getChannelCredentials().get(1).getConfig()).isNull(); + assertThat(serverInfoList.get(0).getChannelCredentials().get(2).getType()) + .isEqualTo("google_default"); + assertThat(serverInfoList.get(0).getChannelCredentials().get(2).getConfig()).isNull(); + assertThat(serverInfoList.get(1).getServerUri()) + .isEqualTo("trafficdirector-bar.googleapis.com:443"); + assertThat(serverInfoList.get(1).getChannelCredentials()).isEmpty(); + assertThat(info.getNode()).isEqualTo( + Node.newBuilder() + .setId("ENVOY_NODE_ID") + .setCluster("ENVOY_CLUSTER") + .setLocality( + Locality.newBuilder() + .setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE")) + .setMetadata( + Struct.newBuilder() + .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", + Value.newBuilder().setStringValue("ENVOY_PORT").build()) + .putFields("TRAFFICDIRECTOR_NETWORK_NAME", + Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()) + .build()) + .setBuildVersion(GrpcUtil.getGrpcBuildVersion()) + .build()); + } + + @Test + public void parseBootstrap_IgnoreIrrelevantFields() throws IOException { + String rawData = "{\n" + + " \"node\": {\n" + + " \"id\": \"ENVOY_NODE_ID\",\n" + + " \"cluster\": \"ENVOY_CLUSTER\",\n" + + " \"locality\": {\n" + + " \"region\": \"ENVOY_REGION\",\n" + + " \"zone\": \"ENVOY_ZONE\",\n" + + " \"sub_zone\": \"ENVOY_SUBZONE\"\n" + + " },\n" + + " \"metadata\": {\n" + + " \"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\",\n" + + " \"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"trafficdirector.googleapis.com:443\",\n" + + " \"ignore\": \"something irrelevant\"," + + " \"channel_creds\": [\n" + + " {\"type\": \"tls\"}, {\"type\": \"loas\"}, {\"type\": \"google_default\"}\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"ignore\": \"something irrelevant\"\n" + + "}"; + + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.getServers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.getServers()); + assertThat(serverInfo.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(serverInfo.getChannelCredentials()).hasSize(3); + assertThat(serverInfo.getChannelCredentials().get(0).getType()).isEqualTo("tls"); + assertThat(serverInfo.getChannelCredentials().get(0).getConfig()).isNull(); + assertThat(serverInfo.getChannelCredentials().get(1).getType()).isEqualTo("loas"); + assertThat(serverInfo.getChannelCredentials().get(1).getConfig()).isNull(); + assertThat(serverInfo.getChannelCredentials().get(2).getType()).isEqualTo("google_default"); + assertThat(serverInfo.getChannelCredentials().get(2).getConfig()).isNull(); assertThat(info.getNode()).isEqualTo( Node.newBuilder() .setId("ENVOY_NODE_ID") @@ -95,14 +226,12 @@ public class BootstrapperTest { @Test public void parseBootstrap_minimumRequiredFields() throws IOException { - String rawData = "{" - + "\"xds_server\": {" - + "\"server_uri\": \"trafficdirector.googleapis.com:443\"" - + "}" + String rawData = "{\n" + + " \"xds_servers\": []\n" + "}"; BootstrapInfo info = Bootstrapper.parseConfig(rawData); - assertThat(info.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(info.getServers()).isEmpty(); assertThat(info.getNode()) .isEqualTo( Node.newBuilder() @@ -112,48 +241,78 @@ public class BootstrapperTest { } @Test - public void parseBootstrap_noXdsServer() throws IOException { - String rawData = "{" - + "\"node\": {" - + "\"id\": \"ENVOY_NODE_ID\"," - + "\"cluster\": \"ENVOY_CLUSTER\"," - + "\"locality\": {" - + "\"region\": \"ENVOY_REGION\", \"zone\": \"ENVOY_ZONE\", \"sub_zone\": \"ENVOY_SUBZONE\"" - + "}," - + "\"metadata\": {" - + "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", " - + "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"" - + "}" - + "}" + public void parseBootstrap_minimalUsableData() throws IOException { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"trafficdirector.googleapis.com:443\"\n" + + " }\n" + + " ]\n" + + "}"; + + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.getServers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.getServers()); + assertThat(serverInfo.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(serverInfo.getChannelCredentials()).isEmpty(); + assertThat(info.getNode()) + .isEqualTo( + Node.newBuilder() + .setBuildVersion( + GrpcUtil.getGrpcBuildVersion()) + .build()); + } + + @Test + public void parseBootstrap_noXdsServers() throws IOException { + String rawData = "{\n" + + " \"node\": {\n" + + " \"id\": \"ENVOY_NODE_ID\",\n" + + " \"cluster\": \"ENVOY_CLUSTER\",\n" + + " \"locality\": {\n" + + " \"region\": \"ENVOY_REGION\",\n" + + " \"zone\": \"ENVOY_ZONE\",\n" + + " \"sub_zone\": \"ENVOY_SUBZONE\"\n" + + " },\n" + + " \"metadata\": {\n" + + " \"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\",\n" + + " \"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"\n" + + " }\n" + + " }\n" + "}"; thrown.expect(IOException.class); - thrown.expectMessage("Invalid bootstrap: 'xds_server' does not exist."); + thrown.expectMessage("Invalid bootstrap: 'xds_servers' does not exist."); Bootstrapper.parseConfig(rawData); } @Test - public void parseBootstrap_noServerUri() throws IOException { + public void parseBootstrap_serverWithoutServerUri() throws IOException { String rawData = "{" - + "\"node\": {" - + "\"id\": \"ENVOY_NODE_ID\"," - + "\"cluster\": \"ENVOY_CLUSTER\"," - + "\"locality\": {" - + "\"region\": \"ENVOY_REGION\", \"zone\": \"ENVOY_ZONE\", \"sub_zone\": \"ENVOY_SUBZONE\"" - + "}," - + "\"metadata\": {" - + "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", " - + "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"" - + "}" - + "}," - + "\"xds_server\": {" - + "\"channel_creds\": " - + "[ {\"type\": \"tls\"}, {\"type\": \"loas\"} ]" - + "} " + + " \"node\": {\n" + + " \"id\": \"ENVOY_NODE_ID\",\n" + + " \"cluster\": \"ENVOY_CLUSTER\",\n" + + " \"locality\": {\n" + + " \"region\": \"ENVOY_REGION\",\n" + + " \"zone\": \"ENVOY_ZONE\",\n" + + " \"sub_zone\": \"ENVOY_SUBZONE\"\n" + + " },\n" + + " \"metadata\": {\n" + + " \"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\",\n" + + " \"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"tls\"}, {\"type\": \"loas\"}\n" + + " ]\n" + + " }\n" + + " ]\n " + "}"; thrown.expect(IOException.class); - thrown.expectMessage("Invalid bootstrap: 'xds_server : server_uri' does not exist."); + thrown.expectMessage("Invalid bootstrap: 'xds_servers' contains unknown server."); Bootstrapper.parseConfig(rawData); } } diff --git a/xds/src/test/java/io/grpc/xds/LookasideLbTest.java b/xds/src/test/java/io/grpc/xds/LookasideLbTest.java index eee1c8e668..01323fb6fc 100644 --- a/xds/src/test/java/io/grpc/xds/LookasideLbTest.java +++ b/xds/src/test/java/io/grpc/xds/LookasideLbTest.java @@ -74,6 +74,7 @@ import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; 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; @@ -489,9 +490,10 @@ public class LookasideLbTest { @Test public void handleResolvedAddress_withBootstrap() throws Exception { - BootstrapInfo bootstrapInfo = new BootstrapInfo( - "trafficdirector.googleapis.com", ImmutableList.of(), - Node.getDefaultInstance()); + List serverList = + ImmutableList.of( + new ServerInfo("trafficdirector.googleapis.com", ImmutableList.of())); + BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, Node.getDefaultInstance()); doReturn(bootstrapInfo).when(bootstrapper).readBootstrap(); String lbConfigRaw = diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 18cea34a45..3458a66e14 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -111,8 +111,11 @@ public class XdsNameResolverTest { Bootstrapper bootstrapper = new Bootstrapper() { @Override public BootstrapInfo readBootstrap() { - return new BootstrapInfo("trafficdirector.googleapis.com", - ImmutableList.of(loasCreds, googleDefaultCreds), FAKE_BOOTSTRAP_NODE); + List serverList = + ImmutableList.of( + new ServerInfo("trafficdirector.googleapis.com", + ImmutableList.of(loasCreds, googleDefaultCreds))); + return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE); } }; XdsNameResolver resolver = new XdsNameResolver("foo.googleapis.com", bootstrapper); @@ -143,6 +146,24 @@ public class XdsNameResolverTest { .containsExactly(loasCreds, googleDefaultCreds); } + @Test + public void resolve_bootstrapProvidesNoTrafficDirectorInfo() { + Bootstrapper bootstrapper = new Bootstrapper() { + @Override + public BootstrapInfo readBootstrap() { + return new BootstrapInfo(ImmutableList.of(), FAKE_BOOTSTRAP_NODE); + } + }; + + XdsNameResolver resolver = new XdsNameResolver("foo.googleapis.com", bootstrapper); + resolver.start(mockListener); + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(null); + verify(mockListener).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(statusCaptor.getValue().getDescription()) + .isEqualTo("No traffic director provided by bootstrap"); + } + @Test public void resolve_failToBootstrap() { Bootstrapper bootstrapper = new Bootstrapper() {