Skip to content

Commit e80c197

Browse files
xds: Use XdsDependencyManager for XdsNameResolver
Contributes to the gRFC A74 effort. https://github.com/grpc/proposal/blob/master/A74-xds-config-tears.md The alternative to using Mockito's ArgumentMatcher is to use Hamcrest. However, Hamcrest did not impress me. ArgumentMatcher is trivial if you don't care about the error message. This fixes a pre-existing issue where ConfigSelector.releaseCluster could revert the LB config back to using cluster manager after releasing all RPCs using a cluster have committed. Co-authored-by: Larry Safran <[email protected]>
1 parent e388ef3 commit e80c197

File tree

10 files changed

+878
-649
lines changed

10 files changed

+878
-649
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2025 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;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.common.base.Preconditions.checkState;
21+
22+
import org.mockito.ArgumentMatcher;
23+
24+
/**
25+
* Mockito matcher for {@link Status}.
26+
*/
27+
public final class StatusMatcher implements ArgumentMatcher<Status> {
28+
public static StatusMatcher statusHasCode(ArgumentMatcher<Status.Code> codeMatcher) {
29+
return new StatusMatcher(codeMatcher, null);
30+
}
31+
32+
public static StatusMatcher statusHasCode(Status.Code code) {
33+
return statusHasCode(new EqualsMatcher<>(code));
34+
}
35+
36+
private final ArgumentMatcher<Status.Code> codeMatcher;
37+
private final ArgumentMatcher<String> descriptionMatcher;
38+
39+
private StatusMatcher(
40+
ArgumentMatcher<Status.Code> codeMatcher,
41+
ArgumentMatcher<String> descriptionMatcher) {
42+
this.codeMatcher = checkNotNull(codeMatcher, "codeMatcher");
43+
this.descriptionMatcher = descriptionMatcher;
44+
}
45+
46+
public StatusMatcher andDescription(ArgumentMatcher<String> descriptionMatcher) {
47+
checkState(this.descriptionMatcher == null, "Already has a description matcher");
48+
return new StatusMatcher(codeMatcher, descriptionMatcher);
49+
}
50+
51+
public StatusMatcher andDescription(String description) {
52+
return andDescription(new EqualsMatcher<>(description));
53+
}
54+
55+
public StatusMatcher andDescriptionContains(String substring) {
56+
return andDescription(new StringContainsMatcher(substring));
57+
}
58+
59+
@Override
60+
public boolean matches(Status status) {
61+
return status != null
62+
&& codeMatcher.matches(status.getCode())
63+
&& (descriptionMatcher == null || descriptionMatcher.matches(status.getDescription()));
64+
}
65+
66+
@Override
67+
public String toString() {
68+
StringBuilder sb = new StringBuilder();
69+
sb.append("{code=");
70+
sb.append(codeMatcher);
71+
if (descriptionMatcher != null) {
72+
sb.append(", description=");
73+
sb.append(descriptionMatcher);
74+
}
75+
sb.append("}");
76+
return sb.toString();
77+
}
78+
79+
// Use instead of lambda for better error message.
80+
static final class EqualsMatcher<T> implements ArgumentMatcher<T> {
81+
private final T obj;
82+
83+
EqualsMatcher(T obj) {
84+
this.obj = checkNotNull(obj, "obj");
85+
}
86+
87+
@Override
88+
public boolean matches(Object other) {
89+
return obj.equals(other);
90+
}
91+
92+
@Override
93+
public String toString() {
94+
return obj.toString();
95+
}
96+
}
97+
98+
static final class StringContainsMatcher implements ArgumentMatcher<String> {
99+
private final String needle;
100+
101+
StringContainsMatcher(String needle) {
102+
this.needle = checkNotNull(needle, "needle");
103+
}
104+
105+
@Override
106+
public boolean matches(String haystack) {
107+
if (haystack == null) {
108+
return false;
109+
}
110+
return haystack.contains(needle);
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return "contains " + needle;
116+
}
117+
}
118+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025 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;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import org.mockito.ArgumentMatcher;
22+
23+
/**
24+
* Mockito matcher for {@link StatusOr}.
25+
*/
26+
public final class StatusOrMatcher<T> implements ArgumentMatcher<StatusOr<T>> {
27+
public static <T> StatusOrMatcher<T> hasValue(ArgumentMatcher<T> valueMatcher) {
28+
return new StatusOrMatcher<T>(checkNotNull(valueMatcher, "valueMatcher"), null);
29+
}
30+
31+
public static <T> StatusOrMatcher<T> hasStatus(ArgumentMatcher<Status> statusMatcher) {
32+
return new StatusOrMatcher<T>(null, checkNotNull(statusMatcher, "statusMatcher"));
33+
}
34+
35+
private final ArgumentMatcher<T> valueMatcher;
36+
private final ArgumentMatcher<Status> statusMatcher;
37+
38+
private StatusOrMatcher(ArgumentMatcher<T> valueMatcher, ArgumentMatcher<Status> statusMatcher) {
39+
this.valueMatcher = valueMatcher;
40+
this.statusMatcher = statusMatcher;
41+
}
42+
43+
@Override
44+
public boolean matches(StatusOr<T> statusOr) {
45+
if (statusOr == null) {
46+
return false;
47+
}
48+
if (statusOr.hasValue() != (valueMatcher != null)) {
49+
return false;
50+
}
51+
if (valueMatcher != null) {
52+
return valueMatcher.matches(statusOr.getValue());
53+
} else {
54+
return statusMatcher.matches(statusOr.getStatus());
55+
}
56+
}
57+
58+
@Override
59+
public String toString() {
60+
if (valueMatcher != null) {
61+
return "{value=" + valueMatcher + "}";
62+
} else {
63+
return "{status=" + statusMatcher + "}";
64+
}
65+
}
66+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ final class XdsAttributes {
3636
static final Attributes.Key<ObjectPool<XdsClient>> XDS_CLIENT_POOL =
3737
Attributes.Key.create("io.grpc.xds.XdsAttributes.xdsClientPool");
3838

39+
/**
40+
* Attribute key for passing around the latest XdsConfig across NameResolver/LoadBalancers.
41+
*/
42+
@NameResolver.ResolutionResultAttr
43+
static final Attributes.Key<XdsConfig> XDS_CONFIG =
44+
Attributes.Key.create("io.grpc.xds.XdsAttributes.xdsConfig");
45+
46+
47+
/**
48+
* Attribute key for passing around the XdsDependencyManager across NameResolver/LoadBalancers.
49+
*/
50+
@NameResolver.ResolutionResultAttr
51+
static final Attributes.Key<XdsConfig.XdsClusterSubscriptionRegistry>
52+
XDS_CLUSTER_SUBSCRIPT_REGISTRY =
53+
Attributes.Key.create("io.grpc.xds.XdsAttributes.xdsConfig.XdsClusterSubscriptionRegistry");
54+
3955
/**
4056
* Attribute key for obtaining the global provider that provides atomics for aggregating
4157
* outstanding RPCs sent to each cluster.

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
2727
import java.io.Closeable;
2828
import java.util.HashMap;
29-
import java.util.List;
3029
import java.util.Map;
3130
import java.util.Objects;
31+
import java.util.Set;
3232

3333
/**
3434
* Represents the xDS configuration tree for a specified Listener.
@@ -178,13 +178,22 @@ public boolean equals(Object obj) {
178178
public StatusOr<EdsUpdate> getEndpoint() {
179179
return endpoint;
180180
}
181+
182+
@Override
183+
public String toString() {
184+
if (endpoint.hasValue()) {
185+
return "EndpointConfig{endpoint=" + endpoint.getValue() + "}";
186+
} else {
187+
return "EndpointConfig{error=" + endpoint.getStatus() + "}";
188+
}
189+
}
181190
}
182191

183192
// The list of leaf clusters for an aggregate cluster.
184193
static final class AggregateConfig implements ClusterChild {
185-
private final List<String> leafNames;
194+
private final Set<String> leafNames;
186195

187-
public AggregateConfig(List<String> leafNames) {
196+
public AggregateConfig(Set<String> leafNames) {
188197
this.leafNames = checkNotNull(leafNames, "leafNames");
189198
}
190199

@@ -234,6 +243,7 @@ XdsConfigBuilder setVirtualHost(VirtualHost virtualHost) {
234243
XdsConfig build() {
235244
checkNotNull(listener, "listener");
236245
checkNotNull(route, "route");
246+
checkNotNull(virtualHost, "virtualHost");
237247
return new XdsConfig(listener, route, clusters, virtualHost);
238248
}
239249
}

0 commit comments

Comments
 (0)