mirror of https://github.com/grpc/grpc-java.git
xds: implement bootstrapping for xDS load balancer for alpha release (#6145)
* Defined a proto message that maps the bootstrap JSON file for convenient parsing purpose.
* Implemented a Bootstrapper class which reads a local bootstrap file.
* Added unit test for Bootstrapper.
* Let xDS load balancer bootstrap from a bootstrap file. Currently not use configurations from bootstrap in xDS communication as the xDS load balancer implementation is undergoing changes. We will integrate it later.
* Added newline at the end of files.
* Added json_name proto field option.
* Replaced all RuntimeException with IOException for bootstrap data errors.
* Changed to use JUnit exception rules in test.
* Use StandardCharSets.UTF_8
* Revert "Let xDS load balancer bootstrap from a bootstrap file. Currently not use configurations from bootstrap in xDS communication as the xDS load balancer implementation is undergoing changes. We will integrate it later."
This reverts commit 37200cdd3c.
* Use initialization-on-demand holder idiom for instantiating Bootstrapper instance.
* Fixed usage of JUnit exception rules.
* Changed lazily instantiated variable to camel case
* Removed unnecessary constructor.
This commit is contained in:
parent
fe77496f76
commit
65321b5a67
|
|
@ -20,11 +20,18 @@ dependencies {
|
|||
compile project(':grpc-protobuf'),
|
||||
project(':grpc-stub'),
|
||||
project(':grpc-core'),
|
||||
project(':grpc-services')
|
||||
project(':grpc-services'),
|
||||
project(':grpc-auth')
|
||||
compile (libraries.protobuf_util) {
|
||||
// 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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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 io.envoyproxy.envoy.api.v2.core.Node;
|
||||
import io.grpc.CallCredentials;
|
||||
import io.grpc.auth.MoreCallCredentials;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Loads configuration information to bootstrap xDS load balancer.
|
||||
*/
|
||||
@Immutable
|
||||
abstract class Bootstrapper {
|
||||
|
||||
private static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP";
|
||||
|
||||
static Bootstrapper getInstance() throws Exception {
|
||||
if (FileBasedBootstrapper.defaultInstance == null) {
|
||||
throw FileBasedBootstrapper.failToBootstrapException;
|
||||
}
|
||||
return FileBasedBootstrapper.defaultInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of the traffic director to be connected to.
|
||||
*/
|
||||
abstract String getBalancerName();
|
||||
|
||||
/**
|
||||
* Returns a {@link Node} message with project/network metadata in it to be included in
|
||||
* xDS requests.
|
||||
*/
|
||||
abstract Node getNode();
|
||||
|
||||
/**
|
||||
* Returns the credentials to use when communicating with the xDS server.
|
||||
*/
|
||||
abstract CallCredentials getCallCredentials();
|
||||
|
||||
@VisibleForTesting
|
||||
static final class FileBasedBootstrapper extends Bootstrapper {
|
||||
|
||||
private static final Exception failToBootstrapException;
|
||||
private static final Bootstrapper defaultInstance;
|
||||
|
||||
private final String balancerName;
|
||||
private final Node node;
|
||||
// TODO(chengyuanzhang): Add configuration for call credentials loaded from bootstrap file.
|
||||
// hard-coded for alpha release.
|
||||
|
||||
static {
|
||||
Bootstrapper instance = null;
|
||||
Exception exception = null;
|
||||
try {
|
||||
instance = new FileBasedBootstrapper(Bootstrapper.readConfig());
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
defaultInstance = instance;
|
||||
failToBootstrapException = exception;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getBalancerName() {
|
||||
return balancerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
CallCredentials getCallCredentials() {
|
||||
return MoreCallCredentials.from(ComputeEngineCredentials.create());
|
||||
}
|
||||
}
|
||||
|
||||
private static Bootstrap 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.");
|
||||
}
|
||||
return parseConfig(new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static Bootstrap parseConfig(String rawData) throws InvalidProtocolBufferException {
|
||||
Bootstrap.Builder bootstrapBuilder = Bootstrap.newBuilder();
|
||||
JsonFormat.parser().merge(rawData, bootstrapBuilder);
|
||||
return bootstrapBuilder.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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"];
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package io.grpc.xds;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
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.FileBasedBootstrapper;
|
||||
import java.io.IOException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link Bootstrapper}. */
|
||||
@RunWith(JUnit4.class)
|
||||
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())))
|
||||
.build();
|
||||
|
||||
Bootstrapper bootstrapper = new FileBasedBootstrapper(config);
|
||||
assertThat(bootstrapper.getBalancerName()).isEqualTo("trafficdirector.googleapis.com:443");
|
||||
assertThat(bootstrapper.getNode())
|
||||
.isEqualTo(
|
||||
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())
|
||||
.build()).build());
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
thrown.expect(IOException.class);
|
||||
thrown.expectMessage("Unexpected api type: REST");
|
||||
new FileBasedBootstrapper(config);
|
||||
}
|
||||
|
||||
@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 {
|
||||
String rawData = "";
|
||||
|
||||
thrown.expect(InvalidProtocolBufferException.class);
|
||||
Bootstrapper.parseConfig(rawData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseBootstrap_invalidNodeProto() throws InvalidProtocolBufferException {
|
||||
String rawData = "{"
|
||||
+ "\"node\": {"
|
||||
+ "\"id\": \"ENVOY_NODE_ID\","
|
||||
+ "\"bad_field\": \"bad_value\""
|
||||
+ "\"locality\": {"
|
||||
+ "\"zone\": \"ENVOY_ZONE\"},"
|
||||
+ "\"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\"} } ]"
|
||||
+ "} "
|
||||
+ "}";
|
||||
|
||||
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);
|
||||
Bootstrapper.parseConfig(rawData);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue