Skip to content

Commit 5e79a26

Browse files
authored
Add support for self config source (#5459)
Motivation: This pull request adds support for SELF config source types. This feature allows the control plane to delegate the decision of which protocol to use to the client. Reference: https://github.com/envoyproxy/envoy/blob/bd18d0fa7790a7e5fe1a95f6ae271ca02614e36c/api/envoy/config/core/v3/config_source.proto#L239 Note that this implementation may not have been completed from envoy side, so we should use this option with caution. ref: envoyproxy/envoy#13951 Modifications: - The dependency relationship between `ResourceNode` and `ConfigSource` has changed. This can be a problem because the configSource specified in `ResourceNode#ConfigSource` can be different from the actual `ConfigSource` used for subscribing. Modified so that `ConfigSource` is computed before creating a `ResourceNode`. - Renamed `BootstrapApiConfigs` to `ConfigSourceMapper` since it better represents the functionality of the class. - Added `parentConfigSource` to the logic of computing a new configSource. - Modified so that subscribed `ResourceNode#configSource` is always non-null. The only case where this is `null` is for static clusters (bootstrap clusters) Result: - SELF type configSource is now supported <!-- Visit this URL to learn more about how to write a pull request description: https://armeria.dev/community/developer-guide#how-to-write-pull-request-description -->
1 parent 33ab3bb commit 5e79a26

File tree

11 files changed

+186
-84
lines changed

11 files changed

+186
-84
lines changed

xds/src/main/java/com/linecorp/armeria/xds/AbstractResourceNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,8 @@ public XdsType type() {
125125
public String name() {
126126
return resourceName;
127127
}
128+
129+
ConfigSourceMapper configSourceMapper() {
130+
return xdsBootstrap.configSourceMapper().withParentConfigSource(configSource);
131+
}
128132
}

xds/src/main/java/com/linecorp/armeria/xds/ClusterResourceNode.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ public void doOnChanged(ClusterXdsResource resource) {
7777
} else if (cluster.hasEdsClusterConfig()) {
7878
final EdsClusterConfig edsClusterConfig = cluster.getEdsClusterConfig();
7979
final String serviceName = edsClusterConfig.getServiceName();
80-
final ConfigSource configSource = edsClusterConfig.getEdsConfig();
80+
final String clusterName = !isNullOrEmpty(serviceName) ? serviceName : cluster.getName();
81+
final ConfigSource configSource = configSourceMapper()
82+
.edsConfigSource(cluster.getEdsClusterConfig().getEdsConfig(), clusterName);
8183
final EndpointResourceNode node =
82-
new EndpointResourceNode(configSource, !isNullOrEmpty(serviceName) ? serviceName
83-
: cluster.getName(),
84-
xdsBootstrap(), resource,
84+
new EndpointResourceNode(configSource, clusterName, xdsBootstrap(), resource,
8585
snapshotWatcher, ResourceNodeType.DYNAMIC);
8686
children().add(node);
8787
xdsBootstrap().subscribe(node);

xds/src/main/java/com/linecorp/armeria/xds/ClusterRoot.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.linecorp.armeria.common.annotation.UnstableApi;
2222

2323
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
24+
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
2425

2526
/**
2627
* A root node representing a {@link Cluster}.
@@ -32,13 +33,14 @@ public final class ClusterRoot extends AbstractRoot<ClusterSnapshot> {
3233

3334
private final ClusterResourceNode node;
3435

35-
ClusterRoot(XdsBootstrapImpl xdsBootstrap, String resourceName) {
36+
ClusterRoot(XdsBootstrapImpl xdsBootstrap, ConfigSourceMapper configSourceMapper, String resourceName) {
3637
super(xdsBootstrap.eventLoop());
3738
final Cluster cluster = xdsBootstrap.bootstrapClusters().cluster(resourceName);
3839
if (cluster != null) {
3940
node = staticCluster(xdsBootstrap, resourceName, this, cluster);
4041
} else {
41-
node = new ClusterResourceNode(null, resourceName, xdsBootstrap,
42+
final ConfigSource configSource = configSourceMapper.cdsConfigSource(null, resourceName);
43+
node = new ClusterResourceNode(configSource, resourceName, xdsBootstrap,
4244
null, this, ResourceNodeType.DYNAMIC);
4345
xdsBootstrap.subscribe(node);
4446
}

xds/src/main/java/com/linecorp/armeria/xds/BootstrapApiConfigs.java renamed to xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceMapper.java

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,25 @@
2222
import io.envoyproxy.envoy.config.bootstrap.v3.Bootstrap.DynamicResources;
2323
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
2424

25-
final class BootstrapApiConfigs {
25+
final class ConfigSourceMapper {
2626

27+
private final Bootstrap bootstrap;
2728
@Nullable
2829
private final ConfigSource bootstrapCdsConfig;
2930
@Nullable
3031
private final ConfigSource bootstrapLdsConfig;
3132
@Nullable
3233
private final ConfigSource bootstrapAdsConfig;
34+
@Nullable
35+
private ConfigSource parentConfigSource;
36+
37+
ConfigSourceMapper(Bootstrap bootstrap) {
38+
this(bootstrap, null);
39+
}
3340

34-
BootstrapApiConfigs(Bootstrap bootstrap) {
35-
if (bootstrap.hasDynamicResources()) {
41+
ConfigSourceMapper(Bootstrap bootstrap, @Nullable ConfigSource parentConfigSource) {
42+
this.bootstrap = bootstrap;
43+
if (this.bootstrap.hasDynamicResources()) {
3644
final DynamicResources dynamicResources = bootstrap.getDynamicResources();
3745
bootstrapCdsConfig = dynamicResources.getCdsConfig();
3846
if (dynamicResources.hasAdsConfig()) {
@@ -48,39 +56,39 @@ final class BootstrapApiConfigs {
4856
bootstrapLdsConfig = null;
4957
bootstrapAdsConfig = null;
5058
}
59+
this.parentConfigSource = parentConfigSource;
5160
}
5261

53-
ConfigSource configSource(XdsType type, String resourceName, ResourceNode<?> node) {
54-
if (type == XdsType.LISTENER) {
55-
return ldsConfigSource(node);
56-
} else if (type == XdsType.ROUTE) {
57-
return rdsConfigSource(node, resourceName);
58-
} else if (type == XdsType.CLUSTER) {
59-
return cdsConfigSource(node, resourceName);
60-
} else {
61-
assert type == XdsType.ENDPOINT;
62-
return edsConfigSource(node, resourceName);
63-
}
62+
ConfigSourceMapper withParentConfigSource(@Nullable ConfigSource parentConfigSource) {
63+
return new ConfigSourceMapper(bootstrap, parentConfigSource);
6464
}
6565

66-
ConfigSource edsConfigSource(ResourceNode<?> node, String resourceName) {
67-
final ConfigSource configSource = node.configSource();
68-
if (configSource != null && configSource.hasApiConfigSource()) {
69-
return configSource;
66+
ConfigSource edsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
67+
if (configSource != null) {
68+
if (configSource.hasApiConfigSource()) {
69+
return configSource;
70+
}
71+
if (configSource.hasSelf() && parentConfigSource != null) {
72+
return parentConfigSource;
73+
}
7074
}
7175
if (bootstrapAdsConfig != null) {
7276
return bootstrapAdsConfig;
7377
}
7478
throw new IllegalArgumentException("Cannot find an EDS config source for " + resourceName);
7579
}
7680

77-
ConfigSource cdsConfigSource(ResourceNode<?> node, String resourceName) {
78-
final ConfigSource configSource = node.configSource();
79-
if (configSource != null && configSource.hasApiConfigSource()) {
80-
return configSource;
81-
}
82-
if (configSource != null && configSource.hasAds() && bootstrapAdsConfig != null) {
83-
return bootstrapAdsConfig;
81+
ConfigSource cdsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
82+
if (configSource != null) {
83+
if (configSource.hasApiConfigSource()) {
84+
return configSource;
85+
}
86+
if (configSource.hasSelf() && parentConfigSource != null) {
87+
return parentConfigSource;
88+
}
89+
if (configSource.hasAds() && bootstrapAdsConfig != null) {
90+
return bootstrapAdsConfig;
91+
}
8492
}
8593
if (bootstrapCdsConfig != null && bootstrapCdsConfig.hasApiConfigSource()) {
8694
return bootstrapCdsConfig;
@@ -91,31 +99,39 @@ ConfigSource cdsConfigSource(ResourceNode<?> node, String resourceName) {
9199
throw new IllegalArgumentException("Cannot find a CDS config source for route: " + resourceName);
92100
}
93101

94-
ConfigSource rdsConfigSource(ResourceNode<?> node, String resourceName) {
95-
final ConfigSource configSource = node.configSource();
96-
if (configSource != null && configSource.hasApiConfigSource()) {
97-
return configSource;
102+
ConfigSource rdsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
103+
if (configSource != null) {
104+
if (configSource.hasApiConfigSource()) {
105+
return configSource;
106+
}
107+
if (configSource.hasSelf() && parentConfigSource != null) {
108+
return parentConfigSource;
109+
}
98110
}
99111
if (bootstrapAdsConfig != null) {
100112
return bootstrapAdsConfig;
101113
}
102114
throw new IllegalArgumentException("Cannot find an RDS config source for route: " + resourceName);
103115
}
104116

105-
ConfigSource ldsConfigSource(ResourceNode<?> node) {
106-
final ConfigSource configSource = node.configSource();
107-
if (configSource != null && configSource.hasApiConfigSource()) {
108-
return configSource;
109-
}
110-
if (configSource != null && configSource.hasAds() && bootstrapAdsConfig != null) {
111-
return bootstrapAdsConfig;
117+
ConfigSource ldsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
118+
if (configSource != null) {
119+
if (configSource.hasApiConfigSource()) {
120+
return configSource;
121+
}
122+
if (configSource.hasSelf() && parentConfigSource != null) {
123+
return parentConfigSource;
124+
}
125+
if (configSource.hasAds() && bootstrapAdsConfig != null) {
126+
return bootstrapAdsConfig;
127+
}
112128
}
113129
if (bootstrapLdsConfig != null && bootstrapLdsConfig.hasApiConfigSource()) {
114130
return bootstrapLdsConfig;
115131
}
116132
if (bootstrapAdsConfig != null) {
117133
return bootstrapAdsConfig;
118134
}
119-
throw new IllegalArgumentException("Cannot find an LDS config source");
135+
throw new IllegalArgumentException("Cannot find an LDS config source for listener: " + resourceName);
120136
}
121137
}

xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceNode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public void doOnChanged(ListenerXdsResource resource) {
5454
if (connectionManager.hasRds()) {
5555
final Rds rds = connectionManager.getRds();
5656
final String routeName = rds.getRouteConfigName();
57-
final ConfigSource configSource = rds.getConfigSource();
57+
final ConfigSource configSource = configSourceMapper()
58+
.rdsConfigSource(rds.getConfigSource(), routeName);
5859
final RouteResourceNode routeResourceNode =
5960
new RouteResourceNode(configSource, routeName, xdsBootstrap(), resource,
6061
snapshotWatcher, ResourceNodeType.DYNAMIC);

xds/src/main/java/com/linecorp/armeria/xds/ListenerRoot.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.linecorp.armeria.common.annotation.UnstableApi;
2020

21+
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
2122
import io.envoyproxy.envoy.config.listener.v3.Listener;
2223

2324
/**
@@ -30,14 +31,17 @@ public final class ListenerRoot extends AbstractRoot<ListenerSnapshot> {
3031

3132
private final ListenerResourceNode node;
3233

33-
ListenerRoot(XdsBootstrapImpl xdsBootstrap, String resourceName, BootstrapListeners bootstrapListeners) {
34+
ListenerRoot(XdsBootstrapImpl xdsBootstrap, ConfigSourceMapper configSourceMapper,
35+
String resourceName, BootstrapListeners bootstrapListeners) {
3436
super(xdsBootstrap.eventLoop());
3537
final ListenerXdsResource listenerXdsResource = bootstrapListeners.staticListeners().get(resourceName);
3638
if (listenerXdsResource != null) {
3739
node = new ListenerResourceNode(null, resourceName, xdsBootstrap, this, ResourceNodeType.STATIC);
3840
node.onChanged(listenerXdsResource);
3941
} else {
40-
node = new ListenerResourceNode(null, resourceName, xdsBootstrap, this, ResourceNodeType.DYNAMIC);
42+
final ConfigSource configSource = configSourceMapper.ldsConfigSource(null, resourceName);
43+
node = new ListenerResourceNode(configSource, resourceName, xdsBootstrap,
44+
this, ResourceNodeType.DYNAMIC);
4145
xdsBootstrap.subscribe(node);
4246
}
4347
}

xds/src/main/java/com/linecorp/armeria/xds/RouteResourceNode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ public void doOnChanged(RouteXdsResource resource) {
8080
route, index++, cluster);
8181
children().add(node);
8282
} else {
83-
node = new ClusterResourceNode(null, clusterName, xdsBootstrap(),
83+
final ConfigSource configSource =
84+
configSourceMapper().cdsConfigSource(null, clusterName);
85+
node = new ClusterResourceNode(configSource, clusterName, xdsBootstrap(),
8486
resource, snapshotWatcher, virtualHost, route,
8587
index++, ResourceNodeType.DYNAMIC);
8688
children().add(node);

xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.linecorp.armeria.xds;
1818

19+
import static com.google.common.base.Preconditions.checkArgument;
1920
import static com.google.common.base.Preconditions.checkState;
2021
import static java.util.Objects.requireNonNull;
2122

@@ -38,8 +39,8 @@ final class XdsBootstrapImpl implements XdsBootstrap {
3839

3940
private final Map<ConfigSource, ConfigSourceClient> clientMap = new HashMap<>();
4041

41-
private final BootstrapApiConfigs bootstrapApiConfigs;
4242
private final BootstrapListeners bootstrapListeners;
43+
private final ConfigSourceMapper configSourceMapper;
4344
private final BootstrapClusters bootstrapClusters;
4445
private final Consumer<GrpcClientBuilder> configClientCustomizer;
4546
private final Node bootstrapNode;
@@ -58,7 +59,7 @@ final class XdsBootstrapImpl implements XdsBootstrap {
5859
Consumer<GrpcClientBuilder> configClientCustomizer) {
5960
this.eventLoop = requireNonNull(eventLoop, "eventLoop");
6061
this.configClientCustomizer = configClientCustomizer;
61-
bootstrapApiConfigs = new BootstrapApiConfigs(bootstrap);
62+
configSourceMapper = new ConfigSourceMapper(bootstrap);
6263
bootstrapListeners = new BootstrapListeners(bootstrap);
6364
bootstrapClusters = new BootstrapClusters(bootstrap, this);
6465
bootstrapNode = bootstrap.hasNode() ? bootstrap.getNode() : Node.getDefaultInstance();
@@ -71,9 +72,9 @@ BootstrapClusters bootstrapClusters() {
7172
void subscribe(ResourceNode<?> node) {
7273
final XdsType type = node.type();
7374
final String name = node.name();
74-
final ConfigSource mappedConfigSource =
75-
bootstrapApiConfigs.configSource(type, name, node);
76-
subscribe0(mappedConfigSource, type, name, node);
75+
final ConfigSource configSource = node.configSource();
76+
checkArgument(configSource != null, "Cannot subscribe to a node without a configSource");
77+
subscribe0(configSource, type, name, node);
7778
}
7879

7980
private void subscribe0(ConfigSource configSource, XdsType type, String resourceName,
@@ -98,25 +99,23 @@ void unsubscribe(ResourceNode<?> node) {
9899
checkState(!closed, "Attempting to unsubscribe to a closed XdsBootstrap");
99100
final XdsType type = node.type();
100101
final String resourceName = node.name();
101-
final ConfigSource mappedConfigSource =
102-
bootstrapApiConfigs.configSource(type, resourceName, node);
103-
final ConfigSourceClient client = clientMap.get(mappedConfigSource);
102+
final ConfigSourceClient client = clientMap.get(node.configSource());
104103
if (client != null && client.removeSubscriber(type, resourceName, node)) {
105104
client.close();
106-
clientMap.remove(mappedConfigSource);
105+
clientMap.remove(node.configSource());
107106
}
108107
}
109108

110109
@Override
111110
public ListenerRoot listenerRoot(String resourceName) {
112111
requireNonNull(resourceName, "resourceName");
113-
return new ListenerRoot(this, resourceName, bootstrapListeners);
112+
return new ListenerRoot(this, configSourceMapper, resourceName, bootstrapListeners);
114113
}
115114

116115
@Override
117116
public ClusterRoot clusterRoot(String resourceName) {
118117
requireNonNull(resourceName, "resourceName");
119-
return new ClusterRoot(this, resourceName);
118+
return new ClusterRoot(this, configSourceMapper, resourceName);
120119
}
121120

122121
@Override
@@ -139,4 +138,8 @@ Map<ConfigSource, ConfigSourceClient> clientMap() {
139138
public EventExecutor eventLoop() {
140139
return eventLoop;
141140
}
141+
142+
ConfigSourceMapper configSourceMapper() {
143+
return configSourceMapper;
144+
}
142145
}

xds/src/main/java/com/linecorp/armeria/xds/XdsConverterUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ static void validateConfigSource(@Nullable ConfigSource configSource) {
3232
if (configSource == null || configSource.equals(ConfigSource.getDefaultInstance())) {
3333
return;
3434
}
35-
checkArgument(configSource.hasAds() || configSource.hasApiConfigSource(),
36-
"Only configSource with Ads or ApiConfigSource is supported for %s", configSource);
35+
checkArgument(configSource.hasAds() || configSource.hasApiConfigSource() || configSource.hasSelf(),
36+
"Only one of (Ads, ApiConfigSource, or Self) type ConfigSource is supported for %s",
37+
configSource);
3738
if (configSource.hasApiConfigSource()) {
3839
final ApiConfigSource apiConfigSource = configSource.getApiConfigSource();
3940
final ApiType apiType = apiConfigSource.getApiType();

0 commit comments

Comments
 (0)