• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19850

05 Jun 2025 03:43PM UTC coverage: 88.616% (-0.03%) from 88.646%
#19850

push

github

ejona86
xds: Fix XdsDepManager aggregate cluster child ordering and loop detection

The children of aggregate clusters have a priority order, so we can't
ever throw them in an ordinary set for later iteration.

This now detects recusion limits only after subscribing, but that
matches our existing behavior in CdsLoadBalancer2. We don't get much
value detecting the limit before subscribing and doing so makes watcher
types more different.

Loops are still a bit broken as they won't be unwatched when orphaned,
as they will form a reference loop. In CdsLoadBalancer2, duplicate
clusters had duplicate watchers so there was single-ownership and
reference cycles couldn't form. Fixing that is a bigger change.

Intermediate aggregate clusters are now included in XdsConfig, just for
simplicity. It doesn't hurt anything whether they are present or
missing. but it required updates to some tests.

34764 of 39230 relevant lines covered (88.62%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.86
/../xds/src/main/java/io/grpc/xds/XdsConfig.java
1
/*
2
 * Copyright 2024 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.collect.ImmutableList;
22
import com.google.common.collect.ImmutableMap;
23
import io.grpc.StatusOr;
24
import io.grpc.xds.XdsClusterResource.CdsUpdate;
25
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
26
import io.grpc.xds.XdsListenerResource.LdsUpdate;
27
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
28
import java.io.Closeable;
29
import java.util.HashMap;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Objects;
33

34
/**
35
 * Represents the xDS configuration tree for a specified Listener.
36
 */
37
final class XdsConfig {
38
  private final LdsUpdate listener;
39
  private final RdsUpdate route;
40
  private final VirtualHost virtualHost;
41
  private final ImmutableMap<String, StatusOr<XdsClusterConfig>> clusters;
42
  private final int hashCode;
43

44
  XdsConfig(LdsUpdate listener, RdsUpdate route, Map<String, StatusOr<XdsClusterConfig>> clusters,
45
            VirtualHost virtualHost) {
46
    this(listener, route, virtualHost, ImmutableMap.copyOf(clusters));
1✔
47
  }
1✔
48

49
  public XdsConfig(LdsUpdate listener, RdsUpdate route, VirtualHost virtualHost,
50
                   ImmutableMap<String, StatusOr<XdsClusterConfig>> clusters) {
1✔
51
    this.listener = listener;
1✔
52
    this.route = route;
1✔
53
    this.virtualHost = virtualHost;
1✔
54
    this.clusters = clusters;
1✔
55

56
    hashCode = Objects.hash(listener, route, virtualHost, clusters);
1✔
57
  }
1✔
58

59
  @Override
60
  public boolean equals(Object obj) {
61
    if (!(obj instanceof XdsConfig)) {
1✔
62
      return false;
×
63
    }
64

65
    XdsConfig o = (XdsConfig) obj;
1✔
66

67
    return hashCode() == o.hashCode() && Objects.equals(listener, o.listener)
1✔
68
        && Objects.equals(route, o.route) && Objects.equals(virtualHost, o.virtualHost)
1✔
69
        && Objects.equals(clusters, o.clusters);
1✔
70
  }
71

72
  @Override
73
  public int hashCode() {
74
    return hashCode;
1✔
75
  }
76

77
  @Override
78
  public String toString() {
79
    StringBuilder builder = new StringBuilder();
1✔
80
    builder.append("XdsConfig{")
1✔
81
        .append("\n  listener=").append(listener)
1✔
82
        .append(",\n  route=").append(route)
1✔
83
        .append(",\n  virtualHost=").append(virtualHost)
1✔
84
        .append(",\n  clusters=").append(clusters)
1✔
85
        .append("\n}");
1✔
86
    return builder.toString();
1✔
87
  }
88

89
  public LdsUpdate getListener() {
90
    return listener;
1✔
91
  }
92

93
  public RdsUpdate getRoute() {
94
    return route;
×
95
  }
96

97
  public VirtualHost getVirtualHost() {
98
    return virtualHost;
1✔
99
  }
100

101
  public ImmutableMap<String, StatusOr<XdsClusterConfig>> getClusters() {
102
    return clusters;
1✔
103
  }
104

105
  static final class XdsClusterConfig {
106
    private final String clusterName;
107
    private final CdsUpdate clusterResource;
108
    private final ClusterChild children; // holds details
109

110
    XdsClusterConfig(String clusterName, CdsUpdate clusterResource, ClusterChild details) {
1✔
111
      this.clusterName = checkNotNull(clusterName, "clusterName");
1✔
112
      this.clusterResource = checkNotNull(clusterResource, "clusterResource");
1✔
113
      this.children = checkNotNull(details, "details");
1✔
114
    }
1✔
115

116
    @Override
117
    public int hashCode() {
118
      return clusterName.hashCode() + clusterResource.hashCode() + children.hashCode();
1✔
119
    }
120

121
    @Override
122
    public boolean equals(Object obj) {
123
      if (!(obj instanceof XdsClusterConfig)) {
1✔
124
        return false;
×
125
      }
126
      XdsClusterConfig o = (XdsClusterConfig) obj;
1✔
127
      return Objects.equals(clusterName, o.clusterName)
1✔
128
          && Objects.equals(clusterResource, o.clusterResource)
1✔
129
          && Objects.equals(children, o.children);
1✔
130
    }
131

132
    @Override
133
    public String toString() {
134
      StringBuilder builder = new StringBuilder();
1✔
135
      builder.append("XdsClusterConfig{clusterName=").append(clusterName)
1✔
136
          .append(", clusterResource=").append(clusterResource)
1✔
137
          .append(", children={").append(children)
1✔
138
          .append("}");
1✔
139
      return builder.toString();
1✔
140
    }
141

142
    public String getClusterName() {
143
      return clusterName;
×
144
    }
145

146
    public CdsUpdate getClusterResource() {
147
      return clusterResource;
1✔
148
    }
149

150
    public ClusterChild getChildren() {
151
      return children;
1✔
152
    }
153

154
    interface ClusterChild {}
155

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

162
      public EndpointConfig(StatusOr<EdsUpdate> endpoint) {
1✔
163
        this.endpoint = checkNotNull(endpoint, "endpoint");
1✔
164
      }
1✔
165

166
      @Override
167
      public int hashCode() {
168
        return endpoint.hashCode();
1✔
169
      }
170

171
      @Override
172
      public boolean equals(Object obj) {
173
        if (!(obj instanceof EndpointConfig)) {
1✔
174
          return false;
×
175
        }
176
        return Objects.equals(endpoint, ((EndpointConfig)obj).endpoint);
1✔
177
      }
178

179
      public StatusOr<EdsUpdate> getEndpoint() {
180
        return endpoint;
1✔
181
      }
182

183
      @Override
184
      public String toString() {
185
        if (endpoint.hasValue()) {
1✔
186
          return "EndpointConfig{endpoint=" + endpoint.getValue() + "}";
1✔
187
        } else {
188
          return "EndpointConfig{error=" + endpoint.getStatus() + "}";
1✔
189
        }
190
      }
191
    }
192

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

197
      public AggregateConfig(List<String> leafNames) {
1✔
198
        this.leafNames = ImmutableList.copyOf(checkNotNull(leafNames, "leafNames"));
1✔
199
      }
1✔
200

201
      public List<String> getLeafNames() {
202
        return leafNames;
1✔
203
      }
204

205
      @Override
206
      public int hashCode() {
207
        return leafNames.hashCode();
1✔
208
      }
209

210
      @Override
211
      public boolean equals(Object obj) {
212
        if (!(obj instanceof AggregateConfig)) {
1✔
213
          return false;
×
214
        }
215
        return Objects.equals(leafNames, ((AggregateConfig) obj).leafNames);
1✔
216
      }
217
    }
218
  }
219

220
  static final class XdsConfigBuilder {
1✔
221
    private LdsUpdate listener;
222
    private RdsUpdate route;
223
    private Map<String, StatusOr<XdsClusterConfig>> clusters = new HashMap<>();
1✔
224
    private VirtualHost virtualHost;
225

226
    XdsConfigBuilder setListener(LdsUpdate listener) {
227
      this.listener = checkNotNull(listener, "listener");
1✔
228
      return this;
1✔
229
    }
230

231
    XdsConfigBuilder setRoute(RdsUpdate route) {
232
      this.route = checkNotNull(route, "route");
1✔
233
      return this;
1✔
234
    }
235

236
    XdsConfigBuilder addCluster(String name, StatusOr<XdsClusterConfig> clusterConfig) {
237
      checkNotNull(name, "name");
1✔
238
      checkNotNull(clusterConfig, "clusterConfig");
1✔
239
      clusters.put(name, clusterConfig);
1✔
240
      return this;
1✔
241
    }
242

243
    XdsConfigBuilder setVirtualHost(VirtualHost virtualHost) {
244
      this.virtualHost = checkNotNull(virtualHost, "virtualHost");
1✔
245
      return this;
1✔
246
    }
247

248
    XdsConfig build() {
249
      checkNotNull(listener, "listener");
1✔
250
      checkNotNull(route, "route");
1✔
251
      checkNotNull(virtualHost, "virtualHost");
1✔
252
      return new XdsConfig(listener, route, clusters, virtualHost);
1✔
253
    }
254
  }
255

256
  public interface XdsClusterSubscriptionRegistry {
257
    Closeable subscribeToCluster(String clusterName);
258
  }
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc