From e448f9c7d3148c5bd58243e141570c36e4169795 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Tue, 1 Oct 2019 14:48:28 -0700 Subject: [PATCH] xds: reimplement bootstrapping with new bootstrap file format design (#6201) This change reimplements Bootstrapper with new design of bootstrap file format, with no longer using ApiConfigSource proto. The new JSON format of the bootstrap file contains a top level "node" and a "xds_server" object, which contains a "server_uri" string and a `channel_creds` list for channel credentials. Unknown fields in the bootstrap file are allowed by ignored so that the implementation does not break when new fields are added in the future. Therefore, we cannot simply create a custom proto file and use proto util to parse it. --- xds/build.gradle | 11 +- .../main/java/io/grpc/xds/Bootstrapper.java | 216 ++++++++++++--- xds/src/main/proto/bootstrap.proto | 39 --- .../java/io/grpc/xds/BootstrapperTest.java | 262 ++++++++++-------- 4 files changed, 324 insertions(+), 204 deletions(-) delete mode 100644 xds/src/main/proto/bootstrap.proto 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); } }