Skip to content

Commit

Permalink
Add support for self config source (#5459)
Browse files Browse the repository at this point in the history
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
-->
  • Loading branch information
jrhee17 authored Apr 3, 2024
1 parent 33ab3bb commit 5e79a26
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,8 @@ public XdsType type() {
public String name() {
return resourceName;
}

ConfigSourceMapper configSourceMapper() {
return xdsBootstrap.configSourceMapper().withParentConfigSource(configSource);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ public void doOnChanged(ClusterXdsResource resource) {
} else if (cluster.hasEdsClusterConfig()) {
final EdsClusterConfig edsClusterConfig = cluster.getEdsClusterConfig();
final String serviceName = edsClusterConfig.getServiceName();
final ConfigSource configSource = edsClusterConfig.getEdsConfig();
final String clusterName = !isNullOrEmpty(serviceName) ? serviceName : cluster.getName();
final ConfigSource configSource = configSourceMapper()
.edsConfigSource(cluster.getEdsClusterConfig().getEdsConfig(), clusterName);
final EndpointResourceNode node =
new EndpointResourceNode(configSource, !isNullOrEmpty(serviceName) ? serviceName
: cluster.getName(),
xdsBootstrap(), resource,
new EndpointResourceNode(configSource, clusterName, xdsBootstrap(), resource,
snapshotWatcher, ResourceNodeType.DYNAMIC);
children().add(node);
xdsBootstrap().subscribe(node);
Expand Down
6 changes: 4 additions & 2 deletions xds/src/main/java/com/linecorp/armeria/xds/ClusterRoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.linecorp.armeria.common.annotation.UnstableApi;

import io.envoyproxy.envoy.config.cluster.v3.Cluster;
import io.envoyproxy.envoy.config.core.v3.ConfigSource;

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

private final ClusterResourceNode node;

ClusterRoot(XdsBootstrapImpl xdsBootstrap, String resourceName) {
ClusterRoot(XdsBootstrapImpl xdsBootstrap, ConfigSourceMapper configSourceMapper, String resourceName) {
super(xdsBootstrap.eventLoop());
final Cluster cluster = xdsBootstrap.bootstrapClusters().cluster(resourceName);
if (cluster != null) {
node = staticCluster(xdsBootstrap, resourceName, this, cluster);
} else {
node = new ClusterResourceNode(null, resourceName, xdsBootstrap,
final ConfigSource configSource = configSourceMapper.cdsConfigSource(null, resourceName);
node = new ClusterResourceNode(configSource, resourceName, xdsBootstrap,
null, this, ResourceNodeType.DYNAMIC);
xdsBootstrap.subscribe(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@
import io.envoyproxy.envoy.config.bootstrap.v3.Bootstrap.DynamicResources;
import io.envoyproxy.envoy.config.core.v3.ConfigSource;

final class BootstrapApiConfigs {
final class ConfigSourceMapper {

private final Bootstrap bootstrap;
@Nullable
private final ConfigSource bootstrapCdsConfig;
@Nullable
private final ConfigSource bootstrapLdsConfig;
@Nullable
private final ConfigSource bootstrapAdsConfig;
@Nullable
private ConfigSource parentConfigSource;

ConfigSourceMapper(Bootstrap bootstrap) {
this(bootstrap, null);
}

BootstrapApiConfigs(Bootstrap bootstrap) {
if (bootstrap.hasDynamicResources()) {
ConfigSourceMapper(Bootstrap bootstrap, @Nullable ConfigSource parentConfigSource) {
this.bootstrap = bootstrap;
if (this.bootstrap.hasDynamicResources()) {
final DynamicResources dynamicResources = bootstrap.getDynamicResources();
bootstrapCdsConfig = dynamicResources.getCdsConfig();
if (dynamicResources.hasAdsConfig()) {
Expand All @@ -48,39 +56,39 @@ final class BootstrapApiConfigs {
bootstrapLdsConfig = null;
bootstrapAdsConfig = null;
}
this.parentConfigSource = parentConfigSource;
}

ConfigSource configSource(XdsType type, String resourceName, ResourceNode<?> node) {
if (type == XdsType.LISTENER) {
return ldsConfigSource(node);
} else if (type == XdsType.ROUTE) {
return rdsConfigSource(node, resourceName);
} else if (type == XdsType.CLUSTER) {
return cdsConfigSource(node, resourceName);
} else {
assert type == XdsType.ENDPOINT;
return edsConfigSource(node, resourceName);
}
ConfigSourceMapper withParentConfigSource(@Nullable ConfigSource parentConfigSource) {
return new ConfigSourceMapper(bootstrap, parentConfigSource);
}

ConfigSource edsConfigSource(ResourceNode<?> node, String resourceName) {
final ConfigSource configSource = node.configSource();
if (configSource != null && configSource.hasApiConfigSource()) {
return configSource;
ConfigSource edsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
if (configSource != null) {
if (configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource.hasSelf() && parentConfigSource != null) {
return parentConfigSource;
}
}
if (bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
}
throw new IllegalArgumentException("Cannot find an EDS config source for " + resourceName);
}

ConfigSource cdsConfigSource(ResourceNode<?> node, String resourceName) {
final ConfigSource configSource = node.configSource();
if (configSource != null && configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource != null && configSource.hasAds() && bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
ConfigSource cdsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
if (configSource != null) {
if (configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource.hasSelf() && parentConfigSource != null) {
return parentConfigSource;
}
if (configSource.hasAds() && bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
}
}
if (bootstrapCdsConfig != null && bootstrapCdsConfig.hasApiConfigSource()) {
return bootstrapCdsConfig;
Expand All @@ -91,31 +99,39 @@ ConfigSource cdsConfigSource(ResourceNode<?> node, String resourceName) {
throw new IllegalArgumentException("Cannot find a CDS config source for route: " + resourceName);
}

ConfigSource rdsConfigSource(ResourceNode<?> node, String resourceName) {
final ConfigSource configSource = node.configSource();
if (configSource != null && configSource.hasApiConfigSource()) {
return configSource;
ConfigSource rdsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
if (configSource != null) {
if (configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource.hasSelf() && parentConfigSource != null) {
return parentConfigSource;
}
}
if (bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
}
throw new IllegalArgumentException("Cannot find an RDS config source for route: " + resourceName);
}

ConfigSource ldsConfigSource(ResourceNode<?> node) {
final ConfigSource configSource = node.configSource();
if (configSource != null && configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource != null && configSource.hasAds() && bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
ConfigSource ldsConfigSource(@Nullable ConfigSource configSource, String resourceName) {
if (configSource != null) {
if (configSource.hasApiConfigSource()) {
return configSource;
}
if (configSource.hasSelf() && parentConfigSource != null) {
return parentConfigSource;
}
if (configSource.hasAds() && bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
}
}
if (bootstrapLdsConfig != null && bootstrapLdsConfig.hasApiConfigSource()) {
return bootstrapLdsConfig;
}
if (bootstrapAdsConfig != null) {
return bootstrapAdsConfig;
}
throw new IllegalArgumentException("Cannot find an LDS config source");
throw new IllegalArgumentException("Cannot find an LDS config source for listener: " + resourceName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public void doOnChanged(ListenerXdsResource resource) {
if (connectionManager.hasRds()) {
final Rds rds = connectionManager.getRds();
final String routeName = rds.getRouteConfigName();
final ConfigSource configSource = rds.getConfigSource();
final ConfigSource configSource = configSourceMapper()
.rdsConfigSource(rds.getConfigSource(), routeName);
final RouteResourceNode routeResourceNode =
new RouteResourceNode(configSource, routeName, xdsBootstrap(), resource,
snapshotWatcher, ResourceNodeType.DYNAMIC);
Expand Down
8 changes: 6 additions & 2 deletions xds/src/main/java/com/linecorp/armeria/xds/ListenerRoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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

import io.envoyproxy.envoy.config.core.v3.ConfigSource;
import io.envoyproxy.envoy.config.listener.v3.Listener;

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

private final ListenerResourceNode node;

ListenerRoot(XdsBootstrapImpl xdsBootstrap, String resourceName, BootstrapListeners bootstrapListeners) {
ListenerRoot(XdsBootstrapImpl xdsBootstrap, ConfigSourceMapper configSourceMapper,
String resourceName, BootstrapListeners bootstrapListeners) {
super(xdsBootstrap.eventLoop());
final ListenerXdsResource listenerXdsResource = bootstrapListeners.staticListeners().get(resourceName);
if (listenerXdsResource != null) {
node = new ListenerResourceNode(null, resourceName, xdsBootstrap, this, ResourceNodeType.STATIC);
node.onChanged(listenerXdsResource);
} else {
node = new ListenerResourceNode(null, resourceName, xdsBootstrap, this, ResourceNodeType.DYNAMIC);
final ConfigSource configSource = configSourceMapper.ldsConfigSource(null, resourceName);
node = new ListenerResourceNode(configSource, resourceName, xdsBootstrap,
this, ResourceNodeType.DYNAMIC);
xdsBootstrap.subscribe(node);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ public void doOnChanged(RouteXdsResource resource) {
route, index++, cluster);
children().add(node);
} else {
node = new ClusterResourceNode(null, clusterName, xdsBootstrap(),
final ConfigSource configSource =
configSourceMapper().cdsConfigSource(null, clusterName);
node = new ClusterResourceNode(configSource, clusterName, xdsBootstrap(),
resource, snapshotWatcher, virtualHost, route,
index++, ResourceNodeType.DYNAMIC);
children().add(node);
Expand Down
25 changes: 14 additions & 11 deletions xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.linecorp.armeria.xds;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;

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

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

private final BootstrapApiConfigs bootstrapApiConfigs;
private final BootstrapListeners bootstrapListeners;
private final ConfigSourceMapper configSourceMapper;
private final BootstrapClusters bootstrapClusters;
private final Consumer<GrpcClientBuilder> configClientCustomizer;
private final Node bootstrapNode;
Expand All @@ -58,7 +59,7 @@ final class XdsBootstrapImpl implements XdsBootstrap {
Consumer<GrpcClientBuilder> configClientCustomizer) {
this.eventLoop = requireNonNull(eventLoop, "eventLoop");
this.configClientCustomizer = configClientCustomizer;
bootstrapApiConfigs = new BootstrapApiConfigs(bootstrap);
configSourceMapper = new ConfigSourceMapper(bootstrap);
bootstrapListeners = new BootstrapListeners(bootstrap);
bootstrapClusters = new BootstrapClusters(bootstrap, this);
bootstrapNode = bootstrap.hasNode() ? bootstrap.getNode() : Node.getDefaultInstance();
Expand All @@ -71,9 +72,9 @@ BootstrapClusters bootstrapClusters() {
void subscribe(ResourceNode<?> node) {
final XdsType type = node.type();
final String name = node.name();
final ConfigSource mappedConfigSource =
bootstrapApiConfigs.configSource(type, name, node);
subscribe0(mappedConfigSource, type, name, node);
final ConfigSource configSource = node.configSource();
checkArgument(configSource != null, "Cannot subscribe to a node without a configSource");
subscribe0(configSource, type, name, node);
}

private void subscribe0(ConfigSource configSource, XdsType type, String resourceName,
Expand All @@ -98,25 +99,23 @@ void unsubscribe(ResourceNode<?> node) {
checkState(!closed, "Attempting to unsubscribe to a closed XdsBootstrap");
final XdsType type = node.type();
final String resourceName = node.name();
final ConfigSource mappedConfigSource =
bootstrapApiConfigs.configSource(type, resourceName, node);
final ConfigSourceClient client = clientMap.get(mappedConfigSource);
final ConfigSourceClient client = clientMap.get(node.configSource());
if (client != null && client.removeSubscriber(type, resourceName, node)) {
client.close();
clientMap.remove(mappedConfigSource);
clientMap.remove(node.configSource());
}
}

@Override
public ListenerRoot listenerRoot(String resourceName) {
requireNonNull(resourceName, "resourceName");
return new ListenerRoot(this, resourceName, bootstrapListeners);
return new ListenerRoot(this, configSourceMapper, resourceName, bootstrapListeners);
}

@Override
public ClusterRoot clusterRoot(String resourceName) {
requireNonNull(resourceName, "resourceName");
return new ClusterRoot(this, resourceName);
return new ClusterRoot(this, configSourceMapper, resourceName);
}

@Override
Expand All @@ -139,4 +138,8 @@ Map<ConfigSource, ConfigSourceClient> clientMap() {
public EventExecutor eventLoop() {
return eventLoop;
}

ConfigSourceMapper configSourceMapper() {
return configSourceMapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ static void validateConfigSource(@Nullable ConfigSource configSource) {
if (configSource == null || configSource.equals(ConfigSource.getDefaultInstance())) {
return;
}
checkArgument(configSource.hasAds() || configSource.hasApiConfigSource(),
"Only configSource with Ads or ApiConfigSource is supported for %s", configSource);
checkArgument(configSource.hasAds() || configSource.hasApiConfigSource() || configSource.hasSelf(),
"Only one of (Ads, ApiConfigSource, or Self) type ConfigSource is supported for %s",
configSource);
if (configSource.hasApiConfigSource()) {
final ApiConfigSource apiConfigSource = configSource.getApiConfigSource();
final ApiType apiType = apiConfigSource.getApiType();
Expand Down
Loading

0 comments on commit 5e79a26

Please sign in to comment.