Skip to content

Commit ebed047

Browse files
committedJul 3, 2024
util: Add GracefulSwitchLb config
This is to replace switchTo(), to allow composing GracefulSwitchLb with other helpers like MultiChildLb. It also prevents users of GracefulSwitchLb from needing to use ServiceConfigUtil.
1 parent 062ebb4 commit ebed047

File tree

3 files changed

+854
-70
lines changed

3 files changed

+854
-70
lines changed
 

‎util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java

+131-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,18 @@
2020
import static com.google.common.base.Preconditions.checkState;
2121

2222
import com.google.common.annotations.VisibleForTesting;
23+
import com.google.common.base.MoreObjects;
24+
import com.google.common.base.Objects;
2325
import io.grpc.ConnectivityState;
2426
import io.grpc.ConnectivityStateInfo;
2527
import io.grpc.ExperimentalApi;
2628
import io.grpc.LoadBalancer;
29+
import io.grpc.LoadBalancerRegistry;
30+
import io.grpc.NameResolver.ConfigOrError;
2731
import io.grpc.Status;
32+
import io.grpc.internal.ServiceConfigUtil;
33+
import java.util.List;
34+
import java.util.Map;
2835
import javax.annotation.Nullable;
2936
import javax.annotation.concurrent.NotThreadSafe;
3037

@@ -33,9 +40,16 @@
3340
* other than READY, the new policy will be swapped into place immediately. Otherwise, the channel
3441
* will keep using the old policy until the new policy reports READY or the old policy exits READY.
3542
*
36-
* <p>The balancer must {@link #switchTo(LoadBalancer.Factory) switch to} a policy prior to {@link
43+
* <p>The child balancer and configuration is specified using service config. Config objects are
44+
* generally created by calling {@link #parseLoadBalancingPolicyConfig(List)} from a
45+
* {@link io.grpc.LoadBalancerProvider#parseLoadBalancingPolicyConfig
46+
* provider's parseLoadBalancingPolicyConfig()} implementation.
47+
*
48+
* <p>Alternatively, the balancer may {@link #switchTo(LoadBalancer.Factory) switch to} a policy
49+
* prior to {@link
3750
* LoadBalancer#handleResolvedAddresses(ResolvedAddresses) handling resolved addresses} for the
38-
* first time.
51+
* first time. This causes graceful switch to ignore the service config and pass through the
52+
* resolved addresses directly to the child policy.
3953
*/
4054
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5999")
4155
@NotThreadSafe // Must be accessed in SynchronizationContext
@@ -90,18 +104,51 @@ public String toString() {
90104
private LoadBalancer pendingLb = defaultBalancer;
91105
private ConnectivityState pendingState;
92106
private SubchannelPicker pendingPicker;
107+
private boolean switchToCalled;
93108

94109
private boolean currentLbIsReady;
95110

96111
public GracefulSwitchLoadBalancer(Helper helper) {
97112
this.helper = checkNotNull(helper, "helper");
98113
}
99114

115+
@Override
116+
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
117+
if (switchToCalled) {
118+
delegate().handleResolvedAddresses(resolvedAddresses);
119+
return;
120+
}
121+
Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig();
122+
switchToInternal(config.childFactory);
123+
delegate().handleResolvedAddresses(
124+
resolvedAddresses.toBuilder()
125+
.setLoadBalancingPolicyConfig(config.childConfig)
126+
.build());
127+
}
128+
129+
@Override
130+
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
131+
if (switchToCalled) {
132+
return delegate().acceptResolvedAddresses(resolvedAddresses);
133+
}
134+
Config config = (Config) resolvedAddresses.getLoadBalancingPolicyConfig();
135+
switchToInternal(config.childFactory);
136+
return delegate().acceptResolvedAddresses(
137+
resolvedAddresses.toBuilder()
138+
.setLoadBalancingPolicyConfig(config.childConfig)
139+
.build());
140+
}
141+
100142
/**
101143
* Gracefully switch to a new policy defined by the given factory, if the given factory isn't
102144
* equal to the current one.
103145
*/
104146
public void switchTo(LoadBalancer.Factory newBalancerFactory) {
147+
switchToCalled = true;
148+
switchToInternal(newBalancerFactory);
149+
}
150+
151+
private void switchToInternal(LoadBalancer.Factory newBalancerFactory) {
105152
checkNotNull(newBalancerFactory, "newBalancerFactory");
106153

107154
if (newBalancerFactory.equals(pendingBalancerFactory)) {
@@ -185,4 +232,86 @@ public void shutdown() {
185232
public String delegateType() {
186233
return delegate().getClass().getSimpleName();
187234
}
235+
236+
/**
237+
* Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch.
238+
*/
239+
public static ConfigOrError parseLoadBalancingPolicyConfig(
240+
List<Map<String, ?>> loadBalancingConfigs) {
241+
return parseLoadBalancingPolicyConfig(
242+
loadBalancingConfigs, LoadBalancerRegistry.getDefaultRegistry());
243+
}
244+
245+
/**
246+
* Provided a JSON list of LoadBalancingConfigs, parse it into a config to pass to GracefulSwitch.
247+
*/
248+
public static ConfigOrError parseLoadBalancingPolicyConfig(
249+
List<Map<String, ?>> loadBalancingConfigs, LoadBalancerRegistry lbRegistry) {
250+
List<ServiceConfigUtil.LbConfig> childConfigCandidates =
251+
ServiceConfigUtil.unwrapLoadBalancingConfigList(loadBalancingConfigs);
252+
if (childConfigCandidates == null || childConfigCandidates.isEmpty()) {
253+
return ConfigOrError.fromError(
254+
Status.INTERNAL.withDescription("No child LB config specified"));
255+
}
256+
ConfigOrError selectedConfig =
257+
ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, lbRegistry);
258+
if (selectedConfig.getError() != null) {
259+
Status error = selectedConfig.getError();
260+
return ConfigOrError.fromError(
261+
Status.INTERNAL
262+
.withCause(error.getCause())
263+
.withDescription(error.getDescription())
264+
.augmentDescription("Failed to select child config"));
265+
}
266+
ServiceConfigUtil.PolicySelection selection =
267+
(ServiceConfigUtil.PolicySelection) selectedConfig.getConfig();
268+
return ConfigOrError.fromConfig(
269+
createLoadBalancingPolicyConfig(selection.getProvider(), selection.getConfig()));
270+
}
271+
272+
/**
273+
* Directly create a config to pass to GracefulSwitch. The object returned is the same as would be
274+
* found in {@code ConfigOrError.getConfig()}.
275+
*/
276+
public static Object createLoadBalancingPolicyConfig(
277+
LoadBalancer.Factory childFactory, @Nullable Object childConfig) {
278+
return new Config(childFactory, childConfig);
279+
}
280+
281+
static final class Config {
282+
final LoadBalancer.Factory childFactory;
283+
@Nullable
284+
final Object childConfig;
285+
286+
public Config(LoadBalancer.Factory childFactory, @Nullable Object childConfig) {
287+
this.childFactory = checkNotNull(childFactory, "childFactory");
288+
this.childConfig = childConfig;
289+
}
290+
291+
@Override
292+
public boolean equals(Object o) {
293+
if (this == o) {
294+
return true;
295+
}
296+
if (!(o instanceof Config)) {
297+
return false;
298+
}
299+
Config that = (Config) o;
300+
return Objects.equal(childFactory, that.childFactory)
301+
&& Objects.equal(childConfig, that.childConfig);
302+
}
303+
304+
@Override
305+
public int hashCode() {
306+
return Objects.hashCode(childFactory, childConfig);
307+
}
308+
309+
@Override
310+
public String toString() {
311+
return MoreObjects.toStringHelper("GracefulSwitchLoadBalancer.Config")
312+
.add("childFactory", childFactory)
313+
.add("childConfig", childConfig)
314+
.toString();
315+
}
316+
}
188317
}

‎util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java

+687-68
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.util;
18+
19+
import io.grpc.LoadBalancerProvider;
20+
21+
/**
22+
* Accessors for white-box testing involving GracefulSwitchLoadBalancer.
23+
*/
24+
public final class GracefulSwitchLoadBalancerAccessor {
25+
private GracefulSwitchLoadBalancerAccessor() {
26+
// Do not instantiate
27+
}
28+
29+
public static LoadBalancerProvider getChildProvider(Object config) {
30+
return (LoadBalancerProvider) ((GracefulSwitchLoadBalancer.Config) config).childFactory;
31+
}
32+
33+
public static Object getChildConfig(Object config) {
34+
return ((GracefulSwitchLoadBalancer.Config) config).childConfig;
35+
}
36+
}

0 commit comments

Comments
 (0)
Please sign in to comment.