Skip to content

Commit

Permalink
xds: add and parse new bootstrap fields for federation
Browse files Browse the repository at this point in the history
  • Loading branch information
dapengzhang0 committed Oct 16, 2021
1 parent 9f644a0 commit 99fca4f
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 41 deletions.
93 changes: 92 additions & 1 deletion xds/src/main/java/io/grpc/xds/Bootstrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,38 @@ public static CertificateProviderInfo create(String pluginName, Map<String, ?> c
}
}

@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.
*/
Expand All @@ -99,17 +131,71 @@ public abstract static class BootstrapInfo {
@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);
Expand All @@ -119,6 +205,11 @@ abstract static class Builder {
abstract Builder serverListenerResourceNameTemplate(
@Nullable String serverListenerResourceNameTemplate);

abstract Builder clientDefaultListenerResourceNameTemplate(
String clientDefaultListenerResourceNameTemplate);

abstract Builder authorities(Map<String, AuthorityInfo> authorities);

abstract BootstrapInfo build();
}
}
Expand Down
135 changes: 95 additions & 40 deletions xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,7 +35,6 @@
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;
Expand Down Expand Up @@ -121,41 +122,14 @@ public BootstrapInfo bootstrap() throws XdsInitializationException {

@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");
Expand Down Expand Up @@ -200,29 +174,110 @@ BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationExceptio
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
Expand Down
Loading

0 comments on commit 99fca4f

Please sign in to comment.