Skip to content

Commit 1f90e0e

Browse files
authored
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.
1 parent e9b0c2e commit 1f90e0e

File tree

3 files changed

+283
-41
lines changed

3 files changed

+283
-41
lines changed

xds/src/main/java/io/grpc/xds/Bootstrapper.java

+92-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,38 @@ public static CertificateProviderInfo create(String pluginName, Map<String, ?> c
8383
}
8484
}
8585

86+
@AutoValue
87+
abstract static class AuthorityInfo {
88+
89+
/**
90+
* A template for the name of the Listener resource to subscribe to for a gRPC client
91+
* channel. Used only when the channel is created using an "xds:" URI with this authority
92+
* name.
93+
*
94+
* <p>The token "%s", if present in this string, will be replaced with %-encoded
95+
* service authority (i.e., the path part of the target URI used to create the gRPC channel).
96+
*
97+
* <p>Return value must start with {@code "xdstp://<authority_name>/"}.
98+
*/
99+
abstract String clientListenerResourceNameTemplate();
100+
101+
/**
102+
* Ordered list of xDS servers to contact for this authority.
103+
*
104+
* <p>If the same server is listed in multiple authorities, the entries will be de-duped (i.e.,
105+
* resources for both authorities will be fetched on the same ADS stream).
106+
*
107+
* <p>If empty, the top-level server list {@link BootstrapInfo#servers()} will be used.
108+
*/
109+
abstract ImmutableList<ServerInfo> xdsServers();
110+
111+
static AuthorityInfo create(
112+
String clientListenerResourceNameTemplate, List<ServerInfo> xdsServers) {
113+
return new AutoValue_Bootstrapper_AuthorityInfo(
114+
clientListenerResourceNameTemplate, ImmutableList.copyOf(xdsServers));
115+
}
116+
}
117+
86118
/**
87119
* Data class containing the results of reading bootstrap.
88120
*/
@@ -99,17 +131,71 @@ public abstract static class BootstrapInfo {
99131
@Nullable
100132
public abstract ImmutableMap<String, CertificateProviderInfo> certProviders();
101133

134+
/**
135+
* A template for the name of the Listener resource to subscribe to for a gRPC server.
136+
*
137+
* <p>If starts with "xdstp:", will be interpreted as a new-style name, in which case the
138+
* authority of the URI will be used to select the relevant configuration in the
139+
* "authorities" map. The token "%s", if present in this string, will be replaced with
140+
* the IP and port on which the server is listening. If the template starts with "xdstp:",
141+
* the replaced string will be %-encoded.
142+
*
143+
* <p>There is no default; if unset, xDS-based server creation fails.
144+
*/
102145
@Nullable
103146
public abstract String serverListenerResourceNameTemplate();
104147

148+
/**
149+
* A template for the name of the Listener resource to subscribe to for a gRPC client channel.
150+
* Used only when the channel is created with an "xds:" URI with no authority.
151+
*
152+
* <p>If starts with "xdstp:", will be interpreted as a new-style name, in which case the
153+
* authority of the URI will be used to select the relevant configuration in the "authorities"
154+
* map.
155+
*
156+
* <p>The token "%s", if present in this string, will be replaced with the service authority
157+
* (i.e., the path part of the target URI used to create the gRPC channel). If the template
158+
* starts with "xdstp:", the replaced string will be %-encoded.
159+
*
160+
* <p>Defaults to {@code "%s"}.
161+
*/
162+
abstract String clientDefaultListenerResourceNameTemplate();
163+
164+
/**
165+
* A map of authority name to corresponding configuration.
166+
*
167+
* <p>This is used in the following cases:
168+
*
169+
* <ul>
170+
* <li>A gRPC client channel is created using an "xds:" URI that includes an
171+
* authority.</li>
172+
*
173+
* <li>A gRPC client channel is created using an "xds:" URI with no authority,
174+
* but the "client_default_listener_resource_name_template" field above turns it into an
175+
* "xdstp:" URI.</li>
176+
*
177+
* <li>A gRPC server is created and the "server_listener_resource_name_template" field is an
178+
* "xdstp:" URI.</li>
179+
* </ul>
180+
*
181+
* <p>In any of those cases, it is an error if the specified authority is not present in this
182+
* map.
183+
*
184+
* <p>Defaults to an empty map.
185+
*/
186+
abstract ImmutableMap<String, AuthorityInfo> authorities();
187+
105188
@VisibleForTesting
106189
static Builder builder() {
107-
return new AutoValue_Bootstrapper_BootstrapInfo.Builder();
190+
return new AutoValue_Bootstrapper_BootstrapInfo.Builder()
191+
.clientDefaultListenerResourceNameTemplate("%s")
192+
.authorities(ImmutableMap.<String, AuthorityInfo>of());
108193
}
109194

110195
@AutoValue.Builder
111196
@VisibleForTesting
112197
abstract static class Builder {
198+
113199
abstract Builder servers(List<ServerInfo> servers);
114200

115201
abstract Builder node(Node node);
@@ -119,6 +205,11 @@ abstract static class Builder {
119205
abstract Builder serverListenerResourceNameTemplate(
120206
@Nullable String serverListenerResourceNameTemplate);
121207

208+
abstract Builder clientDefaultListenerResourceNameTemplate(
209+
String clientDefaultListenerResourceNameTemplate);
210+
211+
abstract Builder authorities(Map<String, AuthorityInfo> authorities);
212+
122213
abstract BootstrapInfo build();
123214
}
124215
}

xds/src/main/java/io/grpc/xds/BootstrapperImpl.java

+95-40
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package io.grpc.xds;
1818

1919
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.collect.ImmutableList;
21+
import com.google.common.collect.ImmutableMap;
2022
import io.grpc.ChannelCredentials;
2123
import io.grpc.InsecureChannelCredentials;
2224
import io.grpc.Internal;
@@ -33,7 +35,6 @@
3335
import java.nio.charset.StandardCharsets;
3436
import java.nio.file.Files;
3537
import java.nio.file.Paths;
36-
import java.util.ArrayList;
3738
import java.util.HashMap;
3839
import java.util.List;
3940
import java.util.Map;
@@ -121,41 +122,14 @@ public BootstrapInfo bootstrap() throws XdsInitializationException {
121122

122123
@Override
123124
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
124-
List<ServerInfo> servers = new ArrayList<>();
125+
BootstrapInfo.Builder builder = BootstrapInfo.builder();
126+
125127
List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
126128
if (rawServerConfigs == null) {
127129
throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
128130
}
129-
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
130-
// TODO(chengyuanzhang): require at least one server URI.
131-
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
132-
for (Map<String, ?> serverConfig : serverConfigList) {
133-
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
134-
if (serverUri == null) {
135-
throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
136-
}
137-
logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
138-
139-
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
140-
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
141-
throw new XdsInitializationException(
142-
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
143-
}
144-
ChannelCredentials channelCredentials =
145-
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
146-
if (channelCredentials == null) {
147-
throw new XdsInitializationException(
148-
"Server " + serverUri + ": no supported channel credentials found");
149-
}
150-
151-
boolean useProtocolV3 = false;
152-
List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
153-
if (serverFeatures != null) {
154-
logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures);
155-
useProtocolV3 = serverFeatures.contains(XDS_V3_SERVER_FEATURE);
156-
}
157-
servers.add(ServerInfo.create(serverUri, channelCredentials, useProtocolV3));
158-
}
131+
List<ServerInfo> servers = parseServerInfos(rawServerConfigs, logger);
132+
builder.servers(servers);
159133

160134
Node.Builder nodeBuilder = Node.newBuilder();
161135
Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
@@ -200,29 +174,110 @@ BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationExceptio
200174
nodeBuilder.setUserAgentName(buildVersion.getUserAgent());
201175
nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion());
202176
nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING);
177+
builder.node(nodeBuilder.build());
203178

204179
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
205-
Map<String, CertificateProviderInfo> certProviders = null;
206180
if (certProvidersBlob != null) {
207-
certProviders = new HashMap<>(certProvidersBlob.size());
181+
logger.log(XdsLogLevel.INFO, "Configured with {0} cert providers", certProvidersBlob.size());
182+
Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
208183
for (String name : certProvidersBlob.keySet()) {
209184
Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
210185
String pluginName =
211186
checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
187+
logger.log(XdsLogLevel.INFO, "cert provider: {0}, plugin name: {1}", name, pluginName);
212188
Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
213189
CertificateProviderInfo certificateProviderInfo =
214190
CertificateProviderInfo.create(pluginName, config);
215191
certProviders.put(name, certificateProviderInfo);
216192
}
193+
builder.certProviders(certProviders);
217194
}
195+
218196
String grpcServerResourceId =
219197
JsonUtil.getString(rawData, "server_listener_resource_name_template");
220-
return BootstrapInfo.builder()
221-
.servers(servers)
222-
.node(nodeBuilder.build())
223-
.certProviders(certProviders)
224-
.serverListenerResourceNameTemplate(grpcServerResourceId)
225-
.build();
198+
logger.log(
199+
XdsLogLevel.INFO, "server_listener_resource_name_template: {0}", grpcServerResourceId);
200+
builder.serverListenerResourceNameTemplate(grpcServerResourceId);
201+
202+
String grpcClientDefaultListener =
203+
JsonUtil.getString(rawData, "client_default_listener_resource_name_template");
204+
logger.log(
205+
XdsLogLevel.INFO, "client_default_listener_resource_name_template: {0}",
206+
grpcClientDefaultListener);
207+
if (grpcClientDefaultListener != null) {
208+
builder.clientDefaultListenerResourceNameTemplate(grpcClientDefaultListener);
209+
}
210+
211+
Map<String, ?> rawAuthoritiesMap =
212+
JsonUtil.getObject(rawData, "authorities");
213+
ImmutableMap.Builder<String, AuthorityInfo> authorityInfoMapBuilder = ImmutableMap.builder();
214+
if (rawAuthoritiesMap != null) {
215+
logger.log(
216+
XdsLogLevel.INFO, "Configured with {0} xDS server authorities", rawAuthoritiesMap.size());
217+
for (String authorityName : rawAuthoritiesMap.keySet()) {
218+
logger.log(XdsLogLevel.INFO, "xDS server authority: {0}", authorityName);
219+
Map<String, ?> rawAuthority = JsonUtil.getObject(rawAuthoritiesMap, authorityName);
220+
String clientListnerTemplate =
221+
JsonUtil.getString(rawAuthority, "client_listener_resource_name_template");
222+
logger.log(
223+
XdsLogLevel.INFO, "client_listener_resource_name_template: {0}", clientListnerTemplate);
224+
String prefix = "xdstp://" + authorityName + "/";
225+
if (clientListnerTemplate == null) {
226+
clientListnerTemplate = prefix + "envoy.config.listener.v3.Listener/%s";
227+
} else if (!clientListnerTemplate.startsWith(prefix)) {
228+
throw new XdsInitializationException(
229+
"client_listener_resource_name_template: '" + clientListnerTemplate
230+
+ "' does not start with " + prefix);
231+
}
232+
List<?> rawAuthorityServers = JsonUtil.getList(rawAuthority, "xds_servers");
233+
List<ServerInfo> authorityServers;
234+
if (rawAuthorityServers == null || rawAuthorityServers.isEmpty()) {
235+
authorityServers = servers;
236+
} else {
237+
authorityServers = parseServerInfos(rawAuthorityServers, logger);
238+
}
239+
authorityInfoMapBuilder.put(
240+
authorityName, AuthorityInfo.create(clientListnerTemplate, authorityServers));
241+
}
242+
builder.authorities(authorityInfoMapBuilder.build());
243+
}
244+
245+
return builder.build();
246+
}
247+
248+
private static List<ServerInfo> parseServerInfos(List<?> rawServerConfigs, XdsLogger logger)
249+
throws XdsInitializationException {
250+
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
251+
ImmutableList.Builder<ServerInfo> servers = ImmutableList.builder();
252+
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
253+
for (Map<String, ?> serverConfig : serverConfigList) {
254+
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
255+
if (serverUri == null) {
256+
throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
257+
}
258+
logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
259+
260+
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
261+
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
262+
throw new XdsInitializationException(
263+
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
264+
}
265+
ChannelCredentials channelCredentials =
266+
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
267+
if (channelCredentials == null) {
268+
throw new XdsInitializationException(
269+
"Server " + serverUri + ": no supported channel credentials found");
270+
}
271+
272+
boolean useProtocolV3 = false;
273+
List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
274+
if (serverFeatures != null) {
275+
logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures);
276+
useProtocolV3 = serverFeatures.contains(XDS_V3_SERVER_FEATURE);
277+
}
278+
servers.add(ServerInfo.create(serverUri, channelCredentials, useProtocolV3));
279+
}
280+
return servers.build();
226281
}
227282

228283
@VisibleForTesting

0 commit comments

Comments
 (0)