diff --git a/xds/build.gradle b/xds/build.gradle index 67902d17d5..a8dc96da27 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -20,9 +20,8 @@ dependencies { compile project(':grpc-protobuf'), project(':grpc-stub'), project(':grpc-core'), - project(':grpc-netty'), - project(':grpc-services'), - project(':grpc-auth') + project(':grpc-services') + compile (libraries.pgv) { // PGV depends on com.google.protobuf:protobuf-java 3.6.1 conflicting with :grpc-protobuf exclude group: 'com.google.protobuf' @@ -31,12 +30,6 @@ dependencies { // prefer 26.0-android from libraries instead of 20.0 exclude group: 'com.google.guava', module: 'guava' } - compile (libraries.google_auth_oauth2_http) { - // prefer 26.0-android from libraries instead of 25.1-android - exclude group: 'com.google.guava', module: 'guava' - // prefer 0.19.2 from libraries instead of 0.18.0 - exclude group: 'io.opencensus', module: 'opencensus-api' - } testCompile project(':grpc-core').sourceSets.test.output diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index e4416b386f..841b2ecb87 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -16,19 +16,24 @@ package io.grpc.xds; -import com.google.auth.oauth2.ComputeEngineCredentials; import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.util.JsonFormat; -import io.envoyproxy.envoy.api.v2.core.ApiConfigSource; -import io.envoyproxy.envoy.api.v2.core.ApiConfigSource.ApiType; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +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.CallCredentials; -import io.grpc.auth.MoreCallCredentials; +import io.grpc.internal.JsonParser; +import io.grpc.internal.JsonUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** @@ -47,9 +52,9 @@ abstract class Bootstrapper { } /** - * Returns the canonical name of the traffic director to be connected to. + * Returns the URI the traffic director to be connected to. */ - abstract String getBalancerName(); + abstract String getServerUri(); /** * Returns a {@link Node} message with project/network metadata in it to be included in @@ -60,7 +65,7 @@ abstract class Bootstrapper { /** * Returns the credentials to use when communicating with the xDS server. */ - abstract CallCredentials getCallCredentials(); + abstract List getChannelCredentials(); @VisibleForTesting static final class FileBasedBootstrapper extends Bootstrapper { @@ -68,10 +73,9 @@ abstract class Bootstrapper { private static final Exception failToBootstrapException; private static final Bootstrapper defaultInstance; - private final String balancerName; + private final String serverUri; private final Node node; - // TODO(chengyuanzhang): Add configuration for call credentials loaded from bootstrap file. - // hard-coded for alpha release. + private final List channelCredsList; static { Bootstrapper instance = null; @@ -86,23 +90,15 @@ abstract class Bootstrapper { } @VisibleForTesting - FileBasedBootstrapper(Bootstrap bootstrapConfig) throws IOException { - ApiConfigSource serverConfig = bootstrapConfig.getXdsServer(); - if (!serverConfig.getApiType().equals(ApiType.GRPC)) { - throw new IOException("Unexpected api type: " + serverConfig.getApiType().toString()); - } - if (serverConfig.getGrpcServicesCount() != 1) { - throw new IOException( - "Unexpected number of gRPC services: expected: 1, actual: " - + serverConfig.getGrpcServicesCount()); - } - balancerName = serverConfig.getGrpcServices(0).getGoogleGrpc().getTargetUri(); - node = bootstrapConfig.getNode(); + FileBasedBootstrapper(BootstrapInfo bootstrapInfo) { + this.serverUri = bootstrapInfo.serverConfig.uri; + this.node = bootstrapInfo.node; + this.channelCredsList = bootstrapInfo.serverConfig.channelCredsList; } @Override - String getBalancerName() { - return balancerName; + String getServerUri() { + return serverUri; } @Override @@ -111,12 +107,12 @@ abstract class Bootstrapper { } @Override - CallCredentials getCallCredentials() { - return MoreCallCredentials.from(ComputeEngineCredentials.create()); + List getChannelCredentials() { + return Collections.unmodifiableList(channelCredsList); } } - private static Bootstrap readConfig() throws IOException { + private static BootstrapInfo readConfig() throws IOException { String filePath = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR); if (filePath == null) { throw new IOException("Environment variable " + BOOTSTRAP_PATH_SYS_ENV_VAR + " not found."); @@ -125,9 +121,161 @@ abstract class Bootstrapper { } @VisibleForTesting - static Bootstrap parseConfig(String rawData) throws InvalidProtocolBufferException { - Bootstrap.Builder bootstrapBuilder = Bootstrap.newBuilder(); - JsonFormat.parser().merge(rawData, bootstrapBuilder); - return bootstrapBuilder.build(); + static BootstrapInfo parseConfig(String rawData) throws IOException { + @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."); + } + // 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); + } + } + ServerConfig serverConfig = new ServerConfig(serverUri, channelCredsOptions); + + Map rawNode = JsonUtil.getObject(rawBootstrap, "node"); + if (rawNode == null) { + throw new IOException("Invalid bootstrap: 'node' does not exist."); + } + // Fields in "node" are not checked. + Node.Builder nodeBuilder = Node.newBuilder(); + String id = JsonUtil.getString(rawNode, "id"); + if (id != null) { + nodeBuilder.setId(id); + } + String cluster = JsonUtil.getString(rawNode, "cluster"); + if (cluster != null) { + nodeBuilder.setCluster(cluster); + } + Map metadata = JsonUtil.getObject(rawNode, "metadata"); + if (metadata != null) { + Struct.Builder structBuilder = Struct.newBuilder(); + for (Map.Entry entry : metadata.entrySet()) { + structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); + } + nodeBuilder.setMetadata(structBuilder); + } + Map rawLocality = JsonUtil.getObject(rawNode, "locality"); + if (rawLocality != null) { + Locality.Builder localityBuilder = Locality.newBuilder(); + String region = JsonUtil.getString(rawLocality, "region"); + if (region == null) { + throw new IOException("Invalid bootstrap: malformed 'node : locality'."); + } + localityBuilder.setRegion(region); + if (rawLocality.containsKey("zone")) { + localityBuilder.setZone(JsonUtil.getString(rawLocality, "zone")); + } + if (rawLocality.containsKey("sub_zone")) { + localityBuilder.setSubZone(JsonUtil.getString(rawLocality, "sub_zone")); + } + nodeBuilder.setLocality(localityBuilder); + } + String buildVersion = JsonUtil.getString(rawNode, "build_version"); + if (buildVersion != null) { + nodeBuilder.setBuildVersion(buildVersion); + } + + return new BootstrapInfo(serverConfig, nodeBuilder.build()); + } + + /** + * Converts Java representation of the given JSON value to protobuf's {@link + * com.google.protobuf.Value} representation. + * + *

The given {@code rawObject} must be a valid JSON value in Java representation, which is + * either a {@code Map}, {@code List}, {@code String}, {@code Double}, + * {@code Boolean}, or {@code null}. + */ + private static Value convertToValue(Object rawObject) { + Value.Builder valueBuilder = Value.newBuilder(); + if (rawObject == null) { + valueBuilder.setNullValue(NullValue.NULL_VALUE); + } else if (rawObject instanceof Double) { + valueBuilder.setNumberValue((Double) rawObject); + } else if (rawObject instanceof String) { + valueBuilder.setStringValue((String) rawObject); + } else if (rawObject instanceof Boolean) { + valueBuilder.setBoolValue((Boolean) rawObject); + } else if (rawObject instanceof Map) { + Struct.Builder structBuilder = Struct.newBuilder(); + @SuppressWarnings("unchecked") + Map map = (Map) rawObject; + for (Map.Entry entry : map.entrySet()) { + structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); + } + valueBuilder.setStructValue(structBuilder); + } else if (rawObject instanceof List) { + ListValue.Builder listBuilder = ListValue.newBuilder(); + List list = (List) rawObject; + for (Object obj : list) { + listBuilder.addValues(convertToValue(obj)); + } + valueBuilder.setListValue(listBuilder); + } + return valueBuilder.build(); + } + + // TODO(chengyuanzhang): May need more complex structure for channel creds config representation. + static class ChannelCreds { + private final String type; + @Nullable + private final Map config; + + @VisibleForTesting + ChannelCreds(String type, @Nullable Map config) { + this.type = type; + this.config = config; + } + + String getType() { + return type; + } + + @Nullable + Map getConfig() { + return config; + } + } + + @VisibleForTesting + static class BootstrapInfo { + final ServerConfig serverConfig; + final Node node; + + @VisibleForTesting + BootstrapInfo(ServerConfig serverConfig, Node node) { + this.serverConfig = serverConfig; + this.node = node; + } + } + + @VisibleForTesting + static class ServerConfig { + final String uri; + final List channelCredsList; + + @VisibleForTesting + ServerConfig(String uri, List channelCredsList) { + this.uri = uri; + this.channelCredsList = channelCredsList; + } } } diff --git a/xds/src/main/proto/bootstrap.proto b/xds/src/main/proto/bootstrap.proto deleted file mode 100644 index 6a45a56125..0000000000 --- a/xds/src/main/proto/bootstrap.proto +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The gRPC Authors -// All rights reserved. -// -// 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. -// An integration test service that covers all the method signature permutations -// of unary/streaming requests/responses. - -syntax = "proto3"; - -package io.grpc.xds; - -option java_outer_classname = "BootstrapProto"; -option java_multiple_files = true; -option java_package = "io.grpc.xds"; - -import "envoy/api/v2/core/base.proto"; -import "envoy/api/v2/core/config_source.proto"; - -// Configurations containing the information needed for xDS load balancer to bootstrap its -// communication with the xDS server. -// This proto message is defined for the convenience of parsing JSON bootstrap file in xDS load -// balancing policy only. It should not be used for any other purposes. -message Bootstrap { - // Metadata to be added to the Node message in xDS requests. - envoy.api.v2.core.Node node = 1 [json_name = "node"]; - - // Configurations including the name of the xDS server to contact, the credentials to use, etc. - envoy.api.v2.core.ApiConfigSource xds_server = 2 [json_name = "xds_server"]; -} diff --git a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java index 07158c9dee..58f9ccea01 100644 --- a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java @@ -18,17 +18,17 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import com.google.protobuf.InvalidProtocolBufferException; +import com.google.common.collect.ImmutableList; import com.google.protobuf.Struct; import com.google.protobuf.Value; -import io.envoyproxy.envoy.api.v2.core.ApiConfigSource; -import io.envoyproxy.envoy.api.v2.core.ApiConfigSource.ApiType; -import io.envoyproxy.envoy.api.v2.core.GrpcService; -import io.envoyproxy.envoy.api.v2.core.GrpcService.GoogleGrpc; import io.envoyproxy.envoy.api.v2.core.Locality; import io.envoyproxy.envoy.api.v2.core.Node; +import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.Bootstrapper.ChannelCreds; import io.grpc.xds.Bootstrapper.FileBasedBootstrapper; +import io.grpc.xds.Bootstrapper.ServerConfig; import java.io.IOException; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -42,162 +42,180 @@ public class BootstrapperTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test - public void validBootstrap() throws IOException { - Bootstrap config = - Bootstrap.newBuilder() - .setNode( - Node.newBuilder() - .setId("ENVOY_NODE_ID") - .setLocality( - Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION")) - .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()))) - .setXdsServer(ApiConfigSource.newBuilder() - .setApiType(ApiType.GRPC) - .addGrpcServices( - GrpcService.newBuilder() - .setGoogleGrpc( - GoogleGrpc.newBuilder() - .setTargetUri("trafficdirector.googleapis.com:443").build()))) + public void validBootstrap() { + List channelCredsList = + ImmutableList.of(new ChannelCreds("TLS", null), new ChannelCreds("LOAS", null)); + ServerConfig serverConfig = + new ServerConfig("trafficdirector.googleapis.com:443", channelCredsList); + + Node node = + 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(); + BootstrapInfo config = new BootstrapInfo(serverConfig, node); Bootstrapper bootstrapper = new FileBasedBootstrapper(config); - assertThat(bootstrapper.getBalancerName()).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(bootstrapper.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443"); assertThat(bootstrapper.getNode()) .isEqualTo( Node.newBuilder() .setId("ENVOY_NODE_ID") - .setLocality(Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION")) + .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()).build()); + .build()) + .build()); + assertThat(bootstrapper.getChannelCredentials()).hasSize(2); + assertThat(bootstrapper.getChannelCredentials().get(0).getType()).isEqualTo("TLS"); + assertThat(bootstrapper.getChannelCredentials().get(0).getConfig()).isNull(); + assertThat(bootstrapper.getChannelCredentials().get(1).getType()).isEqualTo("LOAS"); + assertThat(bootstrapper.getChannelCredentials().get(1).getConfig()).isNull(); } @Test - public void unsupportedApiType() throws IOException { - Bootstrap config = - Bootstrap.newBuilder() - .setNode( - Node.newBuilder() - .setId("ENVOY_NODE_ID") - .setLocality( - Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION")) - .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()))) - .setXdsServer(ApiConfigSource.newBuilder() - .setApiType(ApiType.REST) - .addGrpcServices( - GrpcService.newBuilder() - .setGoogleGrpc( - GoogleGrpc.newBuilder() - .setTargetUri("trafficdirector.googleapis.com:443").build()))) - .build(); + 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\"} ]" + + "} " + + "}"; - thrown.expect(IOException.class); - thrown.expectMessage("Unexpected api type: REST"); - new FileBasedBootstrapper(config); + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.serverConfig.uri).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(info.serverConfig.channelCredsList).hasSize(2); + assertThat(info.serverConfig.channelCredsList.get(0).getType()).isEqualTo("TLS"); + assertThat(info.serverConfig.channelCredsList.get(0).getConfig()).isNull(); + assertThat(info.serverConfig.channelCredsList.get(1).getType()).isEqualTo("LOAS"); + assertThat(info.serverConfig.channelCredsList.get(1).getConfig()).isNull(); + assertThat(info.node).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()) + .build()); } @Test - public void tooManyGrpcServices() throws IOException { - Bootstrap config = - Bootstrap.newBuilder() - .setNode( - Node.newBuilder() - .setId("ENVOY_NODE_ID") - .setLocality( - Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION")) - .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()))) - .setXdsServer(ApiConfigSource.newBuilder() - .setApiType(ApiType.GRPC) - .addGrpcServices( - GrpcService.newBuilder() - .setGoogleGrpc( - GoogleGrpc.newBuilder() - .setTargetUri("trafficdirector.googleapis.com:443").build())) - .addGrpcServices( - GrpcService.newBuilder() - .setGoogleGrpc( - GoogleGrpc.newBuilder() - .setTargetUri("foobar.googleapis.com:443").build())) - ) - .build(); - - thrown.expect(IOException.class); - thrown.expectMessage("Unexpected number of gRPC services: expected: 1, actual: 2"); - new FileBasedBootstrapper(config); - } - - @Test - public void parseBootstrap_emptyData() throws InvalidProtocolBufferException { + public void parseBootstrap_emptyData() throws IOException { String rawData = ""; - thrown.expect(InvalidProtocolBufferException.class); + thrown.expect(IOException.class); Bootstrapper.parseConfig(rawData); } @Test - public void parseBootstrap_invalidNodeProto() throws InvalidProtocolBufferException { + public void parseBootstrap_minimumRequiredFields() throws IOException { + String rawData = "{" + + "\"node\": {}," + + "\"xds_server\": {" + + "\"server_uri\": \"trafficdirector.googleapis.com:443\"" + + "}" + + "}"; + + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.serverConfig.uri).isEqualTo("trafficdirector.googleapis.com:443"); + assertThat(info.node).isEqualTo(Node.getDefaultInstance()); + } + + @Test + public void parseBootstrap_noNode() throws IOException { + String rawData = "{" + + "\"xds_server\": {" + + "\"server_uri\": \"trafficdirector.googleapis.com:443\"," + + "\"channel_creds\": " + + "[ {\"type\": \"TLS\"}, {\"type\": \"LOAS\"} ]" + + "} " + + "}"; + + thrown.expect(IOException.class); + thrown.expectMessage("Invalid bootstrap: 'node' does not exist."); + Bootstrapper.parseConfig(rawData); + } + + @Test + public void parseBootstrap_noXdsServer() throws IOException { String rawData = "{" + "\"node\": {" + "\"id\": \"ENVOY_NODE_ID\"," - + "\"bad_field\": \"bad_value\"" + + "\"cluster\": \"ENVOY_CLUSTER\"," + "\"locality\": {" - + "\"zone\": \"ENVOY_ZONE\"}," + + "\"region\": \"ENVOY_REGION\", \"zone\": \"ENVOY_ZONE\", \"sub_zone\": \"ENVOY_SUBZONE\"" + + "}," + + "\"metadata\": {" + + "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", " + + "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"" + + "}" + + "}" + + "}"; + + thrown.expect(IOException.class); + thrown.expectMessage("Invalid bootstrap: 'xds_server' does not exist."); + Bootstrapper.parseConfig(rawData); + } + + @Test + public void parseBootstrap_noServerUri() 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\": {" - + "\"api_type\": \"GRPC\"," - + "\"grpc_services\": " - + "[ {\"google_grpc\": {\"target_uri\": \"trafficdirector.googleapis.com:443\"} } ]" + + "\"channel_creds\": " + + "[ {\"type\": \"TLS\"}, {\"type\": \"LOAS\"} ]" + "} " + "}"; - thrown.expect(InvalidProtocolBufferException.class); - Bootstrapper.parseConfig(rawData); - } - - @Test - public void parseBootstrap_invalidApiConfigSourceProto() throws InvalidProtocolBufferException { - String rawData = "{" - + "\"node\": {" - + "\"id\": \"ENVOY_NODE_ID\"," - + "\"locality\": {" - + "\"zone\": \"ENVOY_ZONE\"}," - + "\"metadata\": {" - + "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", " - + "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\"" - + "}" - + "}," - + "\"xds_server\": {" - + "\"api_type\": \"GRPC\"," - + "\"bad_field\": \"bad_value\"" - + "\"grpc_services\": " - + "[ {\"google_grpc\": {\"target_uri\": \"trafficdirector.googleapis.com:443\"} } ]" - + "} " - + "}"; - - thrown.expect(InvalidProtocolBufferException.class); + thrown.expect(IOException.class); + thrown.expectMessage("Invalid bootstrap: 'xds_server : server_uri' does not exist."); Bootstrapper.parseConfig(rawData); } }