mirror of https://github.com/grpc/grpc-java.git
xds: add and parse new bootstrap fields for federation (#8608)
Made changes as per "Bootstrap File Changes" section in go/grpc-xds-federation and implemented bootstrap file parsing logic for the change.
This commit is contained in:
parent
e9b0c2e851
commit
1f90e0e28d
|
|
@ -83,6 +83,38 @@ public abstract class Bootstrapper {
|
|||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class AuthorityInfo {
|
||||
|
||||
/**
|
||||
* A template for the name of the Listener resource to subscribe to for a gRPC client
|
||||
* channel. Used only when the channel is created using an "xds:" URI with this authority
|
||||
* name.
|
||||
*
|
||||
* <p>The token "%s", if present in this string, will be replaced with %-encoded
|
||||
* service authority (i.e., the path part of the target URI used to create the gRPC channel).
|
||||
*
|
||||
* <p>Return value must start with {@code "xdstp://<authority_name>/"}.
|
||||
*/
|
||||
abstract String clientListenerResourceNameTemplate();
|
||||
|
||||
/**
|
||||
* Ordered list of xDS servers to contact for this authority.
|
||||
*
|
||||
* <p>If the same server is listed in multiple authorities, the entries will be de-duped (i.e.,
|
||||
* resources for both authorities will be fetched on the same ADS stream).
|
||||
*
|
||||
* <p>If empty, the top-level server list {@link BootstrapInfo#servers()} will be used.
|
||||
*/
|
||||
abstract ImmutableList<ServerInfo> xdsServers();
|
||||
|
||||
static AuthorityInfo create(
|
||||
String clientListenerResourceNameTemplate, List<ServerInfo> xdsServers) {
|
||||
return new AutoValue_Bootstrapper_AuthorityInfo(
|
||||
clientListenerResourceNameTemplate, ImmutableList.copyOf(xdsServers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class containing the results of reading bootstrap.
|
||||
*/
|
||||
|
|
@ -99,17 +131,71 @@ public abstract class Bootstrapper {
|
|||
@Nullable
|
||||
public abstract ImmutableMap<String, CertificateProviderInfo> certProviders();
|
||||
|
||||
/**
|
||||
* A template for the name of the Listener resource to subscribe to for a gRPC server.
|
||||
*
|
||||
* <p>If starts with "xdstp:", will be interpreted as a new-style name, in which case the
|
||||
* authority of the URI will be used to select the relevant configuration in the
|
||||
* "authorities" map. The token "%s", if present in this string, will be replaced with
|
||||
* the IP and port on which the server is listening. If the template starts with "xdstp:",
|
||||
* the replaced string will be %-encoded.
|
||||
*
|
||||
* <p>There is no default; if unset, xDS-based server creation fails.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract String serverListenerResourceNameTemplate();
|
||||
|
||||
/**
|
||||
* A template for the name of the Listener resource to subscribe to for a gRPC client channel.
|
||||
* Used only when the channel is created with an "xds:" URI with no authority.
|
||||
*
|
||||
* <p>If starts with "xdstp:", will be interpreted as a new-style name, in which case the
|
||||
* authority of the URI will be used to select the relevant configuration in the "authorities"
|
||||
* map.
|
||||
*
|
||||
* <p>The token "%s", if present in this string, will be replaced with the service authority
|
||||
* (i.e., the path part of the target URI used to create the gRPC channel). If the template
|
||||
* starts with "xdstp:", the replaced string will be %-encoded.
|
||||
*
|
||||
* <p>Defaults to {@code "%s"}.
|
||||
*/
|
||||
abstract String clientDefaultListenerResourceNameTemplate();
|
||||
|
||||
/**
|
||||
* A map of authority name to corresponding configuration.
|
||||
*
|
||||
* <p>This is used in the following cases:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A gRPC client channel is created using an "xds:" URI that includes an
|
||||
* authority.</li>
|
||||
*
|
||||
* <li>A gRPC client channel is created using an "xds:" URI with no authority,
|
||||
* but the "client_default_listener_resource_name_template" field above turns it into an
|
||||
* "xdstp:" URI.</li>
|
||||
*
|
||||
* <li>A gRPC server is created and the "server_listener_resource_name_template" field is an
|
||||
* "xdstp:" URI.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>In any of those cases, it is an error if the specified authority is not present in this
|
||||
* map.
|
||||
*
|
||||
* <p>Defaults to an empty map.
|
||||
*/
|
||||
abstract ImmutableMap<String, AuthorityInfo> authorities();
|
||||
|
||||
@VisibleForTesting
|
||||
static Builder builder() {
|
||||
return new AutoValue_Bootstrapper_BootstrapInfo.Builder();
|
||||
return new AutoValue_Bootstrapper_BootstrapInfo.Builder()
|
||||
.clientDefaultListenerResourceNameTemplate("%s")
|
||||
.authorities(ImmutableMap.<String, AuthorityInfo>of());
|
||||
}
|
||||
|
||||
@AutoValue.Builder
|
||||
@VisibleForTesting
|
||||
abstract static class Builder {
|
||||
|
||||
abstract Builder servers(List<ServerInfo> servers);
|
||||
|
||||
abstract Builder node(Node node);
|
||||
|
|
@ -119,6 +205,11 @@ public abstract class Bootstrapper {
|
|||
abstract Builder serverListenerResourceNameTemplate(
|
||||
@Nullable String serverListenerResourceNameTemplate);
|
||||
|
||||
abstract Builder clientDefaultListenerResourceNameTemplate(
|
||||
String clientDefaultListenerResourceNameTemplate);
|
||||
|
||||
abstract Builder authorities(Map<String, AuthorityInfo> authorities);
|
||||
|
||||
abstract BootstrapInfo build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package io.grpc.xds;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.grpc.ChannelCredentials;
|
||||
import io.grpc.InsecureChannelCredentials;
|
||||
import io.grpc.Internal;
|
||||
|
|
@ -33,7 +35,6 @@ 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -121,41 +122,14 @@ public class BootstrapperImpl extends Bootstrapper {
|
|||
|
||||
@Override
|
||||
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
|
||||
List<ServerInfo> servers = new ArrayList<>();
|
||||
BootstrapInfo.Builder builder = BootstrapInfo.builder();
|
||||
|
||||
List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
|
||||
if (rawServerConfigs == null) {
|
||||
throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
|
||||
}
|
||||
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
|
||||
// TODO(chengyuanzhang): require at least one server URI.
|
||||
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
|
||||
for (Map<String, ?> serverConfig : serverConfigList) {
|
||||
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
|
||||
if (serverUri == null) {
|
||||
throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
|
||||
}
|
||||
logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
|
||||
|
||||
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
|
||||
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
|
||||
throw new XdsInitializationException(
|
||||
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
|
||||
}
|
||||
ChannelCredentials channelCredentials =
|
||||
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
|
||||
if (channelCredentials == null) {
|
||||
throw new XdsInitializationException(
|
||||
"Server " + serverUri + ": no supported channel credentials found");
|
||||
}
|
||||
|
||||
boolean useProtocolV3 = false;
|
||||
List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
|
||||
if (serverFeatures != null) {
|
||||
logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures);
|
||||
useProtocolV3 = serverFeatures.contains(XDS_V3_SERVER_FEATURE);
|
||||
}
|
||||
servers.add(ServerInfo.create(serverUri, channelCredentials, useProtocolV3));
|
||||
}
|
||||
List<ServerInfo> servers = parseServerInfos(rawServerConfigs, logger);
|
||||
builder.servers(servers);
|
||||
|
||||
Node.Builder nodeBuilder = Node.newBuilder();
|
||||
Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
|
||||
|
|
@ -200,29 +174,110 @@ public class BootstrapperImpl extends Bootstrapper {
|
|||
nodeBuilder.setUserAgentName(buildVersion.getUserAgent());
|
||||
nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion());
|
||||
nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING);
|
||||
builder.node(nodeBuilder.build());
|
||||
|
||||
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
|
||||
Map<String, CertificateProviderInfo> certProviders = null;
|
||||
if (certProvidersBlob != null) {
|
||||
certProviders = new HashMap<>(certProvidersBlob.size());
|
||||
logger.log(XdsLogLevel.INFO, "Configured with {0} cert providers", certProvidersBlob.size());
|
||||
Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
|
||||
for (String name : certProvidersBlob.keySet()) {
|
||||
Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
|
||||
String pluginName =
|
||||
checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
|
||||
logger.log(XdsLogLevel.INFO, "cert provider: {0}, plugin name: {1}", name, pluginName);
|
||||
Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
|
||||
CertificateProviderInfo certificateProviderInfo =
|
||||
CertificateProviderInfo.create(pluginName, config);
|
||||
certProviders.put(name, certificateProviderInfo);
|
||||
}
|
||||
builder.certProviders(certProviders);
|
||||
}
|
||||
|
||||
String grpcServerResourceId =
|
||||
JsonUtil.getString(rawData, "server_listener_resource_name_template");
|
||||
return BootstrapInfo.builder()
|
||||
.servers(servers)
|
||||
.node(nodeBuilder.build())
|
||||
.certProviders(certProviders)
|
||||
.serverListenerResourceNameTemplate(grpcServerResourceId)
|
||||
.build();
|
||||
logger.log(
|
||||
XdsLogLevel.INFO, "server_listener_resource_name_template: {0}", grpcServerResourceId);
|
||||
builder.serverListenerResourceNameTemplate(grpcServerResourceId);
|
||||
|
||||
String grpcClientDefaultListener =
|
||||
JsonUtil.getString(rawData, "client_default_listener_resource_name_template");
|
||||
logger.log(
|
||||
XdsLogLevel.INFO, "client_default_listener_resource_name_template: {0}",
|
||||
grpcClientDefaultListener);
|
||||
if (grpcClientDefaultListener != null) {
|
||||
builder.clientDefaultListenerResourceNameTemplate(grpcClientDefaultListener);
|
||||
}
|
||||
|
||||
Map<String, ?> rawAuthoritiesMap =
|
||||
JsonUtil.getObject(rawData, "authorities");
|
||||
ImmutableMap.Builder<String, AuthorityInfo> authorityInfoMapBuilder = ImmutableMap.builder();
|
||||
if (rawAuthoritiesMap != null) {
|
||||
logger.log(
|
||||
XdsLogLevel.INFO, "Configured with {0} xDS server authorities", rawAuthoritiesMap.size());
|
||||
for (String authorityName : rawAuthoritiesMap.keySet()) {
|
||||
logger.log(XdsLogLevel.INFO, "xDS server authority: {0}", authorityName);
|
||||
Map<String, ?> rawAuthority = JsonUtil.getObject(rawAuthoritiesMap, authorityName);
|
||||
String clientListnerTemplate =
|
||||
JsonUtil.getString(rawAuthority, "client_listener_resource_name_template");
|
||||
logger.log(
|
||||
XdsLogLevel.INFO, "client_listener_resource_name_template: {0}", clientListnerTemplate);
|
||||
String prefix = "xdstp://" + authorityName + "/";
|
||||
if (clientListnerTemplate == null) {
|
||||
clientListnerTemplate = prefix + "envoy.config.listener.v3.Listener/%s";
|
||||
} else if (!clientListnerTemplate.startsWith(prefix)) {
|
||||
throw new XdsInitializationException(
|
||||
"client_listener_resource_name_template: '" + clientListnerTemplate
|
||||
+ "' does not start with " + prefix);
|
||||
}
|
||||
List<?> rawAuthorityServers = JsonUtil.getList(rawAuthority, "xds_servers");
|
||||
List<ServerInfo> authorityServers;
|
||||
if (rawAuthorityServers == null || rawAuthorityServers.isEmpty()) {
|
||||
authorityServers = servers;
|
||||
} else {
|
||||
authorityServers = parseServerInfos(rawAuthorityServers, logger);
|
||||
}
|
||||
authorityInfoMapBuilder.put(
|
||||
authorityName, AuthorityInfo.create(clientListnerTemplate, authorityServers));
|
||||
}
|
||||
builder.authorities(authorityInfoMapBuilder.build());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static List<ServerInfo> parseServerInfos(List<?> rawServerConfigs, XdsLogger logger)
|
||||
throws XdsInitializationException {
|
||||
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
|
||||
ImmutableList.Builder<ServerInfo> servers = ImmutableList.builder();
|
||||
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
|
||||
for (Map<String, ?> serverConfig : serverConfigList) {
|
||||
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
|
||||
if (serverUri == null) {
|
||||
throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
|
||||
}
|
||||
logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
|
||||
|
||||
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
|
||||
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
|
||||
throw new XdsInitializationException(
|
||||
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
|
||||
}
|
||||
ChannelCredentials channelCredentials =
|
||||
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
|
||||
if (channelCredentials == null) {
|
||||
throw new XdsInitializationException(
|
||||
"Server " + serverUri + ": no supported channel credentials found");
|
||||
}
|
||||
|
||||
boolean useProtocolV3 = false;
|
||||
List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
|
||||
if (serverFeatures != null) {
|
||||
logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures);
|
||||
useProtocolV3 = serverFeatures.contains(XDS_V3_SERVER_FEATURE);
|
||||
}
|
||||
servers.add(ServerInfo.create(serverUri, channelCredentials, useProtocolV3));
|
||||
}
|
||||
return servers.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import io.grpc.InsecureChannelCredentials;
|
|||
import io.grpc.TlsChannelCredentials;
|
||||
import io.grpc.internal.GrpcUtil;
|
||||
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
|
||||
import io.grpc.xds.Bootstrapper.AuthorityInfo;
|
||||
import io.grpc.xds.Bootstrapper.BootstrapInfo;
|
||||
import io.grpc.xds.Bootstrapper.ServerInfo;
|
||||
import io.grpc.xds.EnvoyProtoData.Node;
|
||||
|
|
@ -677,6 +678,101 @@ public class BootstrapperImplTest {
|
|||
bootstrapper.bootstrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseClientDefaultListenerResourceNameTemplate() throws Exception {
|
||||
String rawData = "{\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
|
||||
BootstrapInfo info = bootstrapper.bootstrap();
|
||||
assertThat(info.clientDefaultListenerResourceNameTemplate()).isEqualTo("%s");
|
||||
|
||||
rawData = "{\n"
|
||||
+ " \"client_default_listener_resource_name_template\": \"xdstp://a.com/faketype/%s\",\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
|
||||
info = bootstrapper.bootstrap();
|
||||
assertThat(info.clientDefaultListenerResourceNameTemplate())
|
||||
.isEqualTo("xdstp://a.com/faketype/%s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAuthorities() throws Exception {
|
||||
String rawData = "{\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"server_uri\": \"" + SERVER_URI + "\",\n"
|
||||
+ " \"channel_creds\": [\n"
|
||||
+ " {\"type\": \"insecure\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
|
||||
BootstrapInfo info = bootstrapper.bootstrap();
|
||||
assertThat(info.authorities()).isEmpty();
|
||||
|
||||
rawData = "{\n"
|
||||
+ " \"authorities\": {\n"
|
||||
+ " \"a.com\": {\n"
|
||||
+ " \"client_listener_resource_name_template\": \"xdstp://a.com/v1.Listener/id-%s\"\n"
|
||||
+ " }\n"
|
||||
+ " },\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"server_uri\": \"" + SERVER_URI + "\",\n"
|
||||
+ " \"channel_creds\": [\n"
|
||||
+ " {\"type\": \"insecure\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
|
||||
info = bootstrapper.bootstrap();
|
||||
assertThat(info.authorities()).hasSize(1);
|
||||
AuthorityInfo authorityInfo = info.authorities().get("a.com");
|
||||
assertThat(authorityInfo.clientListenerResourceNameTemplate())
|
||||
.isEqualTo("xdstp://a.com/v1.Listener/id-%s");
|
||||
// Defaults to top-level servers.
|
||||
assertThat(authorityInfo.xdsServers()).hasSize(1);
|
||||
assertThat(authorityInfo.xdsServers().get(0).target()).isEqualTo(SERVER_URI);
|
||||
|
||||
rawData = "{\n"
|
||||
+ " \"authorities\": {\n"
|
||||
+ " \"a.com\": {\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"server_uri\": \"td2.googleapis.com:443\",\n"
|
||||
+ " \"channel_creds\": [\n"
|
||||
+ " {\"type\": \"insecure\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " },\n"
|
||||
+ " \"xds_servers\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"server_uri\": \"" + SERVER_URI + "\",\n"
|
||||
+ " \"channel_creds\": [\n"
|
||||
+ " {\"type\": \"insecure\"}\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
|
||||
info = bootstrapper.bootstrap();
|
||||
assertThat(info.authorities()).hasSize(1);
|
||||
authorityInfo = info.authorities().get("a.com");
|
||||
// Defaults to "xdstp://<authority_name>>/envoy.config.listener.v3.Listener/%s"
|
||||
assertThat(authorityInfo.clientListenerResourceNameTemplate())
|
||||
.isEqualTo("xdstp://a.com/envoy.config.listener.v3.Listener/%s");
|
||||
assertThat(authorityInfo.xdsServers()).hasSize(1);
|
||||
assertThat(authorityInfo.xdsServers().get(0).target()).isEqualTo("td2.googleapis.com:443");
|
||||
}
|
||||
|
||||
private static BootstrapperImpl.FileReader createFileReader(
|
||||
final String expectedPath, final String rawData) {
|
||||
return new BootstrapperImpl.FileReader() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue