Skip to content

Commit

Permalink
xds: Change XdsClusterConfig to have children field instead of endpoi…
Browse files Browse the repository at this point in the history
…nt (#11888)

* Change XdsConfig to match spec with a `children` object holding either `a list of leaf cluster names` or `an EdsUpdate`.  Removed intermediate aggregate nodes from `XdsConfig.clusters`.
  • Loading branch information
larry-safran authored Feb 11, 2025
1 parent fc8571a commit ade2dd2
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 47 deletions.
72 changes: 62 additions & 10 deletions xds/src/main/java/io/grpc/xds/XdsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
import java.io.Closeable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -103,19 +104,17 @@ public ImmutableMap<String, StatusOr<XdsClusterConfig>> getClusters() {
static final class XdsClusterConfig {
private final String clusterName;
private final CdsUpdate clusterResource;
private final StatusOr<EdsUpdate> endpoint; //Will be null for non-EDS clusters
private final ClusterChild children; // holds details

XdsClusterConfig(String clusterName, CdsUpdate clusterResource,
StatusOr<EdsUpdate> endpoint) {
XdsClusterConfig(String clusterName, CdsUpdate clusterResource, ClusterChild details) {
this.clusterName = checkNotNull(clusterName, "clusterName");
this.clusterResource = checkNotNull(clusterResource, "clusterResource");
this.endpoint = endpoint;
this.children = checkNotNull(details, "details");
}

@Override
public int hashCode() {
int endpointHash = (endpoint != null) ? endpoint.hashCode() : 0;
return clusterName.hashCode() + clusterResource.hashCode() + endpointHash;
return clusterName.hashCode() + clusterResource.hashCode() + children.hashCode();
}

@Override
Expand All @@ -126,15 +125,16 @@ public boolean equals(Object obj) {
XdsClusterConfig o = (XdsClusterConfig) obj;
return Objects.equals(clusterName, o.clusterName)
&& Objects.equals(clusterResource, o.clusterResource)
&& Objects.equals(endpoint, o.endpoint);
&& Objects.equals(children, o.children);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("XdsClusterConfig{clusterName=").append(clusterName)
.append(", clusterResource=").append(clusterResource)
.append(", endpoint=").append(endpoint).append("}");
.append(", children={").append(children)
.append("}");
return builder.toString();
}

Expand All @@ -146,8 +146,60 @@ public CdsUpdate getClusterResource() {
return clusterResource;
}

public StatusOr<EdsUpdate> getEndpoint() {
return endpoint;
public ClusterChild getChildren() {
return children;
}

interface ClusterChild {}

/** Endpoint info for EDS and LOGICAL_DNS clusters. If there was an
* error, endpoints will be null and resolution_note will be set.
*/
static final class EndpointConfig implements ClusterChild {
private final StatusOr<EdsUpdate> endpoint;

public EndpointConfig(StatusOr<EdsUpdate> endpoint) {
this.endpoint = checkNotNull(endpoint, "endpoint");
}

@Override
public int hashCode() {
return endpoint.hashCode();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof EndpointConfig)) {
return false;
}
return Objects.equals(endpoint, ((EndpointConfig)obj).endpoint);
}

public StatusOr<EdsUpdate> getEndpoint() {
return endpoint;
}
}

// The list of leaf clusters for an aggregate cluster.
static final class AggregateConfig implements ClusterChild {
private final List<String> leafNames;

public AggregateConfig(List<String> leafNames) {
this.leafNames = checkNotNull(leafNames, "leafNames");
}

@Override
public int hashCode() {
return leafNames.hashCode();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof AggregateConfig)) {
return false;
}
return Objects.equals(leafNames, ((AggregateConfig) obj).leafNames);
}
}
}

Expand Down
135 changes: 118 additions & 17 deletions xds/src/main/java/io/grpc/xds/XdsDependencyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,25 @@
import io.grpc.StatusOr;
import io.grpc.SynchronizationContext;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType;
import io.grpc.xds.XdsConfig.XdsClusterConfig.AggregateConfig;
import io.grpc.xds.XdsConfig.XdsClusterConfig.EndpointConfig;
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
import io.grpc.xds.client.XdsClient;
import io.grpc.xds.client.XdsClient.ResourceWatcher;
import io.grpc.xds.client.XdsLogger;
import io.grpc.xds.client.XdsResourceType;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -299,27 +304,123 @@ XdsConfig buildConfig() {
Map<String, ? extends XdsWatcherBase<?>> cdsWatchers =
resourceWatchers.get(CLUSTER_RESOURCE).watchers;

// Iterate CDS watchers
for (XdsWatcherBase<?> watcher : cdsWatchers.values()) {
CdsWatcher cdsWatcher = (CdsWatcher) watcher;
String clusterName = cdsWatcher.resourceName();
StatusOr<XdsClusterResource.CdsUpdate> cdsUpdate = cdsWatcher.getData();
if (cdsUpdate.hasValue()) {
XdsConfig.XdsClusterConfig clusterConfig;
String edsName = cdsUpdate.getValue().edsServiceName();
EdsWatcher edsWatcher = (EdsWatcher) edsWatchers.get(edsName);

// Only EDS type clusters have endpoint data
StatusOr<XdsEndpointResource.EdsUpdate> data =
edsWatcher != null ? edsWatcher.getData() : null;
clusterConfig = new XdsConfig.XdsClusterConfig(clusterName, cdsUpdate.getValue(), data);
builder.addCluster(clusterName, StatusOr.fromValue(clusterConfig));
// Only care about aggregates from LDS/RDS or subscriptions and the leaf clusters
List<String> topLevelClusters =
cdsWatchers.values().stream()
.filter(XdsDependencyManager::isTopLevelCluster)
.map(w -> w.resourceName())
.collect(Collectors.toList());

// Flatten multi-level aggregates into lists of leaf clusters
Set<String> leafNames =
addTopLevelClustersToBuilder(builder, edsWatchers, cdsWatchers, topLevelClusters);

addLeavesToBuilder(builder, edsWatchers, leafNames);

return builder.build();
}

private void addLeavesToBuilder(XdsConfig.XdsConfigBuilder builder,
Map<String, ? extends XdsWatcherBase<?>> edsWatchers,
Set<String> leafNames) {
for (String clusterName : leafNames) {
CdsWatcher cdsWatcher = getCluster(clusterName);
StatusOr<XdsClusterResource.CdsUpdate> cdsUpdateOr = cdsWatcher.getData();

if (cdsUpdateOr.hasValue()) {
XdsClusterResource.CdsUpdate cdsUpdate = cdsUpdateOr.getValue();
if (cdsUpdate.clusterType() == ClusterType.EDS) {
EdsWatcher edsWatcher = (EdsWatcher) edsWatchers.get(cdsUpdate.edsServiceName());
if (edsWatcher != null) {
EndpointConfig child = new EndpointConfig(edsWatcher.getData());
builder.addCluster(clusterName, StatusOr.fromValue(
new XdsConfig.XdsClusterConfig(clusterName, cdsUpdate, child)));
} else {
builder.addCluster(clusterName, StatusOr.fromStatus(Status.UNAVAILABLE.withDescription(
"EDS resource not found for cluster " + clusterName)));
}
} else if (cdsUpdate.clusterType() == ClusterType.LOGICAL_DNS) {
// TODO get the resolved endpoint configuration
builder.addCluster(clusterName, StatusOr.fromValue(
new XdsConfig.XdsClusterConfig(clusterName, cdsUpdate, new EndpointConfig(null))));
}
} else {
builder.addCluster(clusterName, StatusOr.fromStatus(cdsUpdate.getStatus()));
builder.addCluster(clusterName, StatusOr.fromStatus(cdsUpdateOr.getStatus()));
}
}
}

return builder.build();
// Adds the top-level clusters to the builder and returns the leaf cluster names
private Set<String> addTopLevelClustersToBuilder(
XdsConfig.XdsConfigBuilder builder, Map<String, ? extends XdsWatcherBase<?>> edsWatchers,
Map<String, ? extends XdsWatcherBase<?>> cdsWatchers, List<String> topLevelClusters) {

Set<String> leafClusterNames = new HashSet<>();
for (String clusterName : topLevelClusters) {
CdsWatcher cdsWatcher = (CdsWatcher) cdsWatchers.get(clusterName);
StatusOr<XdsClusterResource.CdsUpdate> cdsWatcherDataOr = cdsWatcher.getData();
if (!cdsWatcher.hasDataValue()) {
builder.addCluster(clusterName, StatusOr.fromStatus(cdsWatcherDataOr.getStatus()));
continue;
}

XdsClusterResource.CdsUpdate cdsUpdate = cdsWatcherDataOr.getValue();
XdsConfig.XdsClusterConfig.ClusterChild child;
switch (cdsUpdate.clusterType()) {
case AGGREGATE:
List<String> leafNames = getLeafNames(cdsUpdate);
child = new AggregateConfig(leafNames);
leafClusterNames.addAll(leafNames);
break;
case EDS:
EdsWatcher edsWatcher = (EdsWatcher) edsWatchers.get(cdsUpdate.edsServiceName());
if (edsWatcher != null) {
child = new EndpointConfig(edsWatcher.getData());
} else {
builder.addCluster(clusterName, StatusOr.fromStatus(Status.UNAVAILABLE.withDescription(
"EDS resource not found for cluster " + clusterName)));
continue;
}
break;
case LOGICAL_DNS:
// TODO get the resolved endpoint configuration
child = new EndpointConfig(null);
break;
default:
throw new IllegalStateException("Unexpected value: " + cdsUpdate.clusterType());
}
builder.addCluster(clusterName, StatusOr.fromValue(
new XdsConfig.XdsClusterConfig(clusterName, cdsUpdate, child)));
}

return leafClusterNames;
}

private List<String> getLeafNames(XdsClusterResource.CdsUpdate cdsUpdate) {
List<String> childNames = new ArrayList<>();

for (String cluster : cdsUpdate.prioritizedClusterNames()) {
StatusOr<XdsClusterResource.CdsUpdate> data = getCluster(cluster).getData();
if (data == null || !data.hasValue() || data.getValue() == null) {
childNames.add(cluster);
continue;
}
if (data.getValue().clusterType() == ClusterType.AGGREGATE) {
childNames.addAll(getLeafNames(data.getValue()));
} else {
childNames.add(cluster);
}
}

return childNames;
}

private static boolean isTopLevelCluster(XdsWatcherBase<?> cdsWatcher) {
if (! (cdsWatcher instanceof CdsWatcher)) {
return false;
}
return ((CdsWatcher)cdsWatcher).parentContexts.values().stream()
.anyMatch(depth -> depth == 1);
}

@Override
Expand Down
Loading

0 comments on commit ade2dd2

Please sign in to comment.