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() {