xds: ClusterManagerLB must update child configuration

While child LB policies are unlikey to change for each cluster name (RLS
returns regular cluster names, so should be unique), and the
configuration for CDS policies won't change, RLS configuration can
definitely change.
This commit is contained in:
Eric Anderson 2024-07-25 18:07:11 -07:00
parent d034a56cb0
commit 10d6002cbd
4 changed files with 56 additions and 45 deletions

View File

@ -23,11 +23,11 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import io.grpc.ConnectivityState; import io.grpc.ConnectivityState;
import io.grpc.InternalLogId; import io.grpc.InternalLogId;
import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancer;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext;
import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.SynchronizationContext.ScheduledHandle;
import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.util.MultiChildLoadBalancer; import io.grpc.util.MultiChildLoadBalancer;
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
import io.grpc.xds.client.XdsLogger; import io.grpc.xds.client.XdsLogger;
@ -71,7 +71,10 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
@Override @Override
protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses, protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses,
Object childConfig) { Object unusedChildConfig) {
ClusterManagerConfig config = (ClusterManagerConfig)
resolvedAddresses.getLoadBalancingPolicyConfig();
Object childConfig = config.childPolicies.get(key);
return resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build(); return resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build();
} }
@ -81,13 +84,12 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
resolvedAddresses.getLoadBalancingPolicyConfig(); resolvedAddresses.getLoadBalancingPolicyConfig();
Map<Object, ChildLbState> newChildPolicies = new HashMap<>(); Map<Object, ChildLbState> newChildPolicies = new HashMap<>();
if (config != null) { if (config != null) {
for (Entry<String, PolicySelection> entry : config.childPolicies.entrySet()) { for (String key : config.childPolicies.keySet()) {
ChildLbState child = getChildLbState(entry.getKey()); ChildLbState child = getChildLbState(key);
if (child == null) { if (child == null) {
child = new ClusterManagerLbState(entry.getKey(), child = new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE, null);
entry.getValue().getProvider(), entry.getValue().getConfig());
} }
newChildPolicies.put(entry.getKey(), child); newChildPolicies.put(key, child);
} }
} }
logger.log( logger.log(
@ -108,8 +110,8 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
resolvedAddresses.getLoadBalancingPolicyConfig(); resolvedAddresses.getLoadBalancingPolicyConfig();
ClusterManagerConfig lastConfig = (ClusterManagerConfig) ClusterManagerConfig lastConfig = (ClusterManagerConfig)
lastResolvedAddresses.getLoadBalancingPolicyConfig(); lastResolvedAddresses.getLoadBalancingPolicyConfig();
Map<String, PolicySelection> adjChildPolicies = new HashMap<>(config.childPolicies); Map<String, Object> adjChildPolicies = new HashMap<>(config.childPolicies);
for (Entry<String, PolicySelection> entry : lastConfig.childPolicies.entrySet()) { for (Entry<String, Object> entry : lastConfig.childPolicies.entrySet()) {
ClusterManagerLbState state = (ClusterManagerLbState) getChildLbState(entry.getKey()); ClusterManagerLbState state = (ClusterManagerLbState) getChildLbState(entry.getKey());
if (adjChildPolicies.containsKey(entry.getKey())) { if (adjChildPolicies.containsKey(entry.getKey())) {
if (state.deletionTimer != null) { if (state.deletionTimer != null) {
@ -202,9 +204,9 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
@Nullable @Nullable
ScheduledHandle deletionTimer; ScheduledHandle deletionTimer;
public ClusterManagerLbState(Object key, LoadBalancerProvider policyProvider, public ClusterManagerLbState(Object key, LoadBalancer.Factory policyFactory,
Object childConfig) { Object childConfig) {
super(key, policyProvider, childConfig); super(key, policyFactory, childConfig);
} }
@Override @Override
@ -237,8 +239,8 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
public void run() { public void run() {
ClusterManagerConfig config = (ClusterManagerConfig) ClusterManagerConfig config = (ClusterManagerConfig)
lastResolvedAddresses.getLoadBalancingPolicyConfig(); lastResolvedAddresses.getLoadBalancingPolicyConfig();
Map<String, PolicySelection> childPolicies = new HashMap<>(config.childPolicies); Map<String, Object> childPolicies = new HashMap<>(config.childPolicies);
PolicySelection removed = childPolicies.remove(getKey()); Object removed = childPolicies.remove(getKey());
assert removed != null; assert removed != null;
config = new ClusterManagerConfig(childPolicies); config = new ClusterManagerConfig(childPolicies);
lastResolvedAddresses = lastResolvedAddresses =
@ -276,4 +278,13 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
} }
} }
} }
static final class GracefulSwitchLoadBalancerFactory extends LoadBalancer.Factory {
static final LoadBalancer.Factory INSTANCE = new GracefulSwitchLoadBalancerFactory();
@Override
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
return new GracefulSwitchLoadBalancer(helper);
}
}
} }

View File

@ -26,12 +26,9 @@ import io.grpc.LoadBalancerRegistry;
import io.grpc.NameResolver.ConfigOrError; import io.grpc.NameResolver.ConfigOrError;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.internal.JsonUtil; import io.grpc.internal.JsonUtil;
import io.grpc.internal.ServiceConfigUtil; import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.internal.ServiceConfigUtil.LbConfig;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -73,7 +70,7 @@ public class ClusterManagerLoadBalancerProvider extends LoadBalancerProvider {
@Override @Override
public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) { public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) {
Map<String, PolicySelection> parsedChildPolicies = new LinkedHashMap<>(); Map<String, Object> parsedChildPolicies = new LinkedHashMap<>();
try { try {
Map<String, ?> childPolicies = JsonUtil.getObject(rawConfig, "childPolicy"); Map<String, ?> childPolicies = JsonUtil.getObject(rawConfig, "childPolicy");
if (childPolicies == null || childPolicies.isEmpty()) { if (childPolicies == null || childPolicies.isEmpty()) {
@ -86,27 +83,19 @@ public class ClusterManagerLoadBalancerProvider extends LoadBalancerProvider {
return ConfigOrError.fromError(Status.INTERNAL.withDescription( return ConfigOrError.fromError(Status.INTERNAL.withDescription(
"No config for child " + name + " in cluster_manager LB policy: " + rawConfig)); "No config for child " + name + " in cluster_manager LB policy: " + rawConfig));
} }
List<LbConfig> childConfigCandidates =
ServiceConfigUtil.unwrapLoadBalancingConfigList(
JsonUtil.getListOfObjects(childPolicy, "lbPolicy"));
if (childConfigCandidates == null || childConfigCandidates.isEmpty()) {
return ConfigOrError.fromError(Status.INTERNAL.withDescription(
"No config specified for child " + name + " in cluster_manager Lb policy: "
+ rawConfig));
}
LoadBalancerRegistry registry = LoadBalancerRegistry registry =
lbRegistry != null ? lbRegistry : LoadBalancerRegistry.getDefaultRegistry(); lbRegistry != null ? lbRegistry : LoadBalancerRegistry.getDefaultRegistry();
ConfigOrError selectedConfig = ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, registry); JsonUtil.getListOfObjects(childPolicy, "lbPolicy"), registry);
if (selectedConfig.getError() != null) { if (childConfig.getError() != null) {
Status error = selectedConfig.getError(); Status error = childConfig.getError();
return ConfigOrError.fromError( return ConfigOrError.fromError(
Status.INTERNAL Status.INTERNAL
.withCause(error.getCause()) .withCause(error.getCause())
.withDescription(error.getDescription()) .withDescription(error.getDescription())
.augmentDescription("Failed to select config for child " + name)); .augmentDescription("Failed to parse config for child " + name));
} }
parsedChildPolicies.put(name, (PolicySelection) selectedConfig.getConfig()); parsedChildPolicies.put(name, childConfig.getConfig());
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
return ConfigOrError.fromError( return ConfigOrError.fromError(
@ -122,9 +111,9 @@ public class ClusterManagerLoadBalancerProvider extends LoadBalancerProvider {
} }
static class ClusterManagerConfig { static class ClusterManagerConfig {
final Map<String, PolicySelection> childPolicies; final Map<String, Object> childPolicies;
ClusterManagerConfig(Map<String, PolicySelection> childPolicies) { ClusterManagerConfig(Map<String, Object> childPolicies) {
this.childPolicies = Collections.unmodifiableMap(childPolicies); this.childPolicies = Collections.unmodifiableMap(childPolicies);
} }

View File

@ -26,7 +26,7 @@ import io.grpc.NameResolver.ConfigOrError;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.Status.Code; import io.grpc.Status.Code;
import io.grpc.internal.JsonParser; import io.grpc.internal.JsonParser;
import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@ -133,10 +133,9 @@ public class ClusterManagerLoadBalancerProviderTest {
assertThat(config.childPolicies) assertThat(config.childPolicies)
.containsExactly( .containsExactly(
"child1", "child1",
new PolicySelection( GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProviderFoo, fooConfig),
lbProviderFoo, fooConfig),
"child2", "child2",
new PolicySelection(lbProviderBar, barConfig)); GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProviderBar, barConfig));
} }
@Test @Test

View File

@ -52,10 +52,11 @@ import io.grpc.Status.Code;
import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext;
import io.grpc.internal.FakeClock; import io.grpc.internal.FakeClock;
import io.grpc.internal.PickSubchannelArgsImpl; import io.grpc.internal.PickSubchannelArgsImpl;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.testing.TestMethodDescriptors; import io.grpc.testing.TestMethodDescriptors;
import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -288,16 +289,27 @@ public class ClusterManagerLoadBalancerTest {
.build()); .build());
} }
// Prevent ClusterManagerLB from detecting different providers even when the configuration is the
// same.
private Map<List<Object>, FakeLoadBalancerProvider> fakeLoadBalancerProviderCache
= new HashMap<>();
private ClusterManagerConfig buildConfig(Map<String, String> childPolicies, boolean failing) { private ClusterManagerConfig buildConfig(Map<String, String> childPolicies, boolean failing) {
Map<String, PolicySelection> childPolicySelections = new LinkedHashMap<>(); Map<String, Object> childConfigs = new LinkedHashMap<>();
for (String name : childPolicies.keySet()) { for (String name : childPolicies.keySet()) {
String childPolicyName = childPolicies.get(name); String childPolicyName = childPolicies.get(name);
Object childConfig = lbConfigInventory.get(name); Object childConfig = lbConfigInventory.get(name);
PolicySelection policy = FakeLoadBalancerProvider lbProvider =
new PolicySelection(new FakeLoadBalancerProvider(childPolicyName, failing), childConfig); fakeLoadBalancerProviderCache.get(Arrays.asList(childPolicyName, failing));
childPolicySelections.put(name, policy); if (lbProvider == null) {
lbProvider = new FakeLoadBalancerProvider(childPolicyName, failing);
fakeLoadBalancerProviderCache.put(Arrays.asList(childPolicyName, failing), lbProvider);
}
Object policy =
GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProvider, childConfig);
childConfigs.put(name, policy);
} }
return new ClusterManagerConfig(childPolicySelections); return new ClusterManagerConfig(childConfigs);
} }
private static PickResult pickSubchannel(SubchannelPicker picker, String clusterName) { private static PickResult pickSubchannel(SubchannelPicker picker, String clusterName) {