mirror of https://github.com/grpc/grpc-java.git
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.
This commit is contained in:
parent
9668102f05
commit
e448f9c7d3
|
|
@ -20,9 +20,8 @@ dependencies {
|
||||||
compile project(':grpc-protobuf'),
|
compile project(':grpc-protobuf'),
|
||||||
project(':grpc-stub'),
|
project(':grpc-stub'),
|
||||||
project(':grpc-core'),
|
project(':grpc-core'),
|
||||||
project(':grpc-netty'),
|
project(':grpc-services')
|
||||||
project(':grpc-services'),
|
|
||||||
project(':grpc-auth')
|
|
||||||
compile (libraries.pgv) {
|
compile (libraries.pgv) {
|
||||||
// PGV depends on com.google.protobuf:protobuf-java 3.6.1 conflicting with :grpc-protobuf
|
// PGV depends on com.google.protobuf:protobuf-java 3.6.1 conflicting with :grpc-protobuf
|
||||||
exclude group: 'com.google.protobuf'
|
exclude group: 'com.google.protobuf'
|
||||||
|
|
@ -31,12 +30,6 @@ dependencies {
|
||||||
// prefer 26.0-android from libraries instead of 20.0
|
// prefer 26.0-android from libraries instead of 20.0
|
||||||
exclude group: 'com.google.guava', module: 'guava'
|
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
|
testCompile project(':grpc-core').sourceSets.test.output
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,24 @@
|
||||||
|
|
||||||
package io.grpc.xds;
|
package io.grpc.xds;
|
||||||
|
|
||||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.ListValue;
|
||||||
import com.google.protobuf.util.JsonFormat;
|
import com.google.protobuf.NullValue;
|
||||||
import io.envoyproxy.envoy.api.v2.core.ApiConfigSource;
|
import com.google.protobuf.Struct;
|
||||||
import io.envoyproxy.envoy.api.v2.core.ApiConfigSource.ApiType;
|
import com.google.protobuf.Value;
|
||||||
|
import io.envoyproxy.envoy.api.v2.core.Locality;
|
||||||
import io.envoyproxy.envoy.api.v2.core.Node;
|
import io.envoyproxy.envoy.api.v2.core.Node;
|
||||||
import io.grpc.CallCredentials;
|
import io.grpc.internal.JsonParser;
|
||||||
import io.grpc.auth.MoreCallCredentials;
|
import io.grpc.internal.JsonUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
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;
|
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
|
* 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.
|
* Returns the credentials to use when communicating with the xDS server.
|
||||||
*/
|
*/
|
||||||
abstract CallCredentials getCallCredentials();
|
abstract List<ChannelCreds> getChannelCredentials();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final class FileBasedBootstrapper extends Bootstrapper {
|
static final class FileBasedBootstrapper extends Bootstrapper {
|
||||||
|
|
@ -68,10 +73,9 @@ abstract class Bootstrapper {
|
||||||
private static final Exception failToBootstrapException;
|
private static final Exception failToBootstrapException;
|
||||||
private static final Bootstrapper defaultInstance;
|
private static final Bootstrapper defaultInstance;
|
||||||
|
|
||||||
private final String balancerName;
|
private final String serverUri;
|
||||||
private final Node node;
|
private final Node node;
|
||||||
// TODO(chengyuanzhang): Add configuration for call credentials loaded from bootstrap file.
|
private final List<ChannelCreds> channelCredsList;
|
||||||
// hard-coded for alpha release.
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Bootstrapper instance = null;
|
Bootstrapper instance = null;
|
||||||
|
|
@ -86,23 +90,15 @@ abstract class Bootstrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
FileBasedBootstrapper(Bootstrap bootstrapConfig) throws IOException {
|
FileBasedBootstrapper(BootstrapInfo bootstrapInfo) {
|
||||||
ApiConfigSource serverConfig = bootstrapConfig.getXdsServer();
|
this.serverUri = bootstrapInfo.serverConfig.uri;
|
||||||
if (!serverConfig.getApiType().equals(ApiType.GRPC)) {
|
this.node = bootstrapInfo.node;
|
||||||
throw new IOException("Unexpected api type: " + serverConfig.getApiType().toString());
|
this.channelCredsList = bootstrapInfo.serverConfig.channelCredsList;
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getBalancerName() {
|
String getServerUri() {
|
||||||
return balancerName;
|
return serverUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -111,12 +107,12 @@ abstract class Bootstrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CallCredentials getCallCredentials() {
|
List<ChannelCreds> getChannelCredentials() {
|
||||||
return MoreCallCredentials.from(ComputeEngineCredentials.create());
|
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);
|
String filePath = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR);
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
throw new IOException("Environment variable " + BOOTSTRAP_PATH_SYS_ENV_VAR + " not found.");
|
throw new IOException("Environment variable " + BOOTSTRAP_PATH_SYS_ENV_VAR + " not found.");
|
||||||
|
|
@ -125,9 +121,161 @@ abstract class Bootstrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static Bootstrap parseConfig(String rawData) throws InvalidProtocolBufferException {
|
static BootstrapInfo parseConfig(String rawData) throws IOException {
|
||||||
Bootstrap.Builder bootstrapBuilder = Bootstrap.newBuilder();
|
@SuppressWarnings("unchecked")
|
||||||
JsonFormat.parser().merge(rawData, bootstrapBuilder);
|
Map<String, ?> rawBootstrap = (Map<String, ?>) JsonParser.parse(rawData);
|
||||||
return bootstrapBuilder.build();
|
|
||||||
|
Map<String, ?> 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<ChannelCreds> channelCredsOptions = new ArrayList<>();
|
||||||
|
List<?> rawChannelCredsList = JsonUtil.getList(rawServerConfig, "channel_creds");
|
||||||
|
// List of channel creds is optional.
|
||||||
|
if (rawChannelCredsList != null) {
|
||||||
|
List<Map<String, ?>> channelCredsList = JsonUtil.checkObjectList(rawChannelCredsList);
|
||||||
|
for (Map<String, ?> 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<String, ?> 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<String, ?> metadata = JsonUtil.getObject(rawNode, "metadata");
|
||||||
|
if (metadata != null) {
|
||||||
|
Struct.Builder structBuilder = Struct.newBuilder();
|
||||||
|
for (Map.Entry<String, ?> entry : metadata.entrySet()) {
|
||||||
|
structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue()));
|
||||||
|
}
|
||||||
|
nodeBuilder.setMetadata(structBuilder);
|
||||||
|
}
|
||||||
|
Map<String, ?> 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.
|
||||||
|
*
|
||||||
|
* <p>The given {@code rawObject} must be a valid JSON value in Java representation, which is
|
||||||
|
* either a {@code Map<String, ?>}, {@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<String, ?> map = (Map<String, ?>) rawObject;
|
||||||
|
for (Map.Entry<String, ?> 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<String, ?> config;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ChannelCreds(String type, @Nullable Map<String, ?> config) {
|
||||||
|
this.type = type;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Map<String, ?> 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<ChannelCreds> channelCredsList;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ServerConfig(String uri, List<ChannelCreds> channelCredsList) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.channelCredsList = channelCredsList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"];
|
|
||||||
}
|
|
||||||
|
|
@ -18,17 +18,17 @@ package io.grpc.xds;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.Struct;
|
||||||
import com.google.protobuf.Value;
|
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.Locality;
|
||||||
import io.envoyproxy.envoy.api.v2.core.Node;
|
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.FileBasedBootstrapper;
|
||||||
|
import io.grpc.xds.Bootstrapper.ServerConfig;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
@ -42,162 +42,180 @@ public class BootstrapperTest {
|
||||||
@Rule public ExpectedException thrown = ExpectedException.none();
|
@Rule public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validBootstrap() throws IOException {
|
public void validBootstrap() {
|
||||||
Bootstrap config =
|
List<ChannelCreds> channelCredsList =
|
||||||
Bootstrap.newBuilder()
|
ImmutableList.of(new ChannelCreds("TLS", null), new ChannelCreds("LOAS", null));
|
||||||
.setNode(
|
ServerConfig serverConfig =
|
||||||
|
new ServerConfig("trafficdirector.googleapis.com:443", channelCredsList);
|
||||||
|
|
||||||
|
Node node =
|
||||||
Node.newBuilder()
|
Node.newBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.setId("ENVOY_NODE_ID")
|
||||||
|
.setCluster("ENVOY_CLUSTER")
|
||||||
.setLocality(
|
.setLocality(
|
||||||
Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION"))
|
Locality.newBuilder()
|
||||||
|
.setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE"))
|
||||||
.setMetadata(
|
.setMetadata(
|
||||||
Struct.newBuilder()
|
Struct.newBuilder()
|
||||||
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
||||||
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
||||||
Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())))
|
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())))
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
BootstrapInfo config = new BootstrapInfo(serverConfig, node);
|
||||||
Bootstrapper bootstrapper = new FileBasedBootstrapper(config);
|
Bootstrapper bootstrapper = new FileBasedBootstrapper(config);
|
||||||
assertThat(bootstrapper.getBalancerName()).isEqualTo("trafficdirector.googleapis.com:443");
|
assertThat(bootstrapper.getServerUri()).isEqualTo("trafficdirector.googleapis.com:443");
|
||||||
assertThat(bootstrapper.getNode())
|
assertThat(bootstrapper.getNode())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
Node.newBuilder()
|
Node.newBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.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(
|
.setMetadata(
|
||||||
Struct.newBuilder()
|
Struct.newBuilder()
|
||||||
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
||||||
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
||||||
Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())
|
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
|
@Test
|
||||||
public void unsupportedApiType() throws IOException {
|
public void parseBootstrap_validData() throws IOException {
|
||||||
Bootstrap config =
|
String rawData = "{"
|
||||||
Bootstrap.newBuilder()
|
+ "\"node\": {"
|
||||||
.setNode(
|
+ "\"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\"} ]"
|
||||||
|
+ "} "
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
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()
|
Node.newBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.setId("ENVOY_NODE_ID")
|
||||||
|
.setCluster("ENVOY_CLUSTER")
|
||||||
.setLocality(
|
.setLocality(
|
||||||
Locality.newBuilder().setZone("ENVOY_ZONE").setRegion("ENVOY_REGION"))
|
Locality.newBuilder()
|
||||||
|
.setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE"))
|
||||||
.setMetadata(
|
.setMetadata(
|
||||||
Struct.newBuilder()
|
Struct.newBuilder()
|
||||||
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
.putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
Value.newBuilder().setStringValue("ENVOY_PORT").build())
|
||||||
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
.putFields("TRAFFICDIRECTOR_NETWORK_NAME",
|
||||||
Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())))
|
Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())
|
||||||
.setXdsServer(ApiConfigSource.newBuilder()
|
.build())
|
||||||
.setApiType(ApiType.REST)
|
.build());
|
||||||
.addGrpcServices(
|
|
||||||
GrpcService.newBuilder()
|
|
||||||
.setGoogleGrpc(
|
|
||||||
GoogleGrpc.newBuilder()
|
|
||||||
.setTargetUri("trafficdirector.googleapis.com:443").build())))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
thrown.expect(IOException.class);
|
|
||||||
thrown.expectMessage("Unexpected api type: REST");
|
|
||||||
new FileBasedBootstrapper(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tooManyGrpcServices() throws IOException {
|
public void parseBootstrap_emptyData() 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 {
|
|
||||||
String rawData = "";
|
String rawData = "";
|
||||||
|
|
||||||
thrown.expect(InvalidProtocolBufferException.class);
|
thrown.expect(IOException.class);
|
||||||
Bootstrapper.parseConfig(rawData);
|
Bootstrapper.parseConfig(rawData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 = "{"
|
String rawData = "{"
|
||||||
+ "\"node\": {"
|
+ "\"node\": {"
|
||||||
+ "\"id\": \"ENVOY_NODE_ID\","
|
+ "\"id\": \"ENVOY_NODE_ID\","
|
||||||
+ "\"bad_field\": \"bad_value\""
|
+ "\"cluster\": \"ENVOY_CLUSTER\","
|
||||||
+ "\"locality\": {"
|
+ "\"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\": {"
|
+ "\"metadata\": {"
|
||||||
+ "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", "
|
+ "\"TRAFFICDIRECTOR_INTERCEPTION_PORT\": \"ENVOY_PORT\", "
|
||||||
+ "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\""
|
+ "\"TRAFFICDIRECTOR_NETWORK_NAME\": \"VPC_NETWORK_NAME\""
|
||||||
+ "}"
|
+ "}"
|
||||||
+ "},"
|
+ "},"
|
||||||
+ "\"xds_server\": {"
|
+ "\"xds_server\": {"
|
||||||
+ "\"api_type\": \"GRPC\","
|
+ "\"channel_creds\": "
|
||||||
+ "\"grpc_services\": "
|
+ "[ {\"type\": \"TLS\"}, {\"type\": \"LOAS\"} ]"
|
||||||
+ "[ {\"google_grpc\": {\"target_uri\": \"trafficdirector.googleapis.com:443\"} } ]"
|
|
||||||
+ "} "
|
+ "} "
|
||||||
+ "}";
|
+ "}";
|
||||||
|
|
||||||
thrown.expect(InvalidProtocolBufferException.class);
|
thrown.expect(IOException.class);
|
||||||
Bootstrapper.parseConfig(rawData);
|
thrown.expectMessage("Invalid bootstrap: 'xds_server : server_uri' does not exist.");
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
Bootstrapper.parseConfig(rawData);
|
Bootstrapper.parseConfig(rawData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue