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

grpc / grpc-java / #18890

09 Nov 2023 09:46PM UTC coverage: 88.206% (-0.06%) from 88.264%
#18890

push

github

web-flow
xds:Make Ring Hash LB a petiole policy (#10610)

* Update picker logic per A61 that it no longer pays attention to the first 2 elements, but rather takes the first ring element not in TF and uses that.
---------
Pulled in by rebase:
Eric Anderson  (android: Remove unneeded proguard rule 44723b6)
Terry Wilson (stub: Deprecate StreamObservers b5434e8)

30334 of 34390 relevant lines covered (88.21%)

0.88 hits per line

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

96.0
/../xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java
1
/*
2
 * Copyright 2020 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
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
21

22
import com.google.common.annotations.VisibleForTesting;
23
import com.google.common.base.MoreObjects;
24
import io.grpc.InternalLogId;
25
import io.grpc.LoadBalancerProvider;
26
import io.grpc.Status;
27
import io.grpc.SynchronizationContext;
28
import io.grpc.SynchronizationContext.ScheduledHandle;
29
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
30
import io.grpc.util.MultiChildLoadBalancer;
31
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
32
import io.grpc.xds.XdsLogger.XdsLogLevel;
33
import java.util.HashMap;
34
import java.util.Map;
35
import java.util.Map.Entry;
36
import java.util.concurrent.ScheduledExecutorService;
37
import java.util.concurrent.TimeUnit;
38
import javax.annotation.Nullable;
39

40
/**
41
 * The top-level load balancing policy for use in XDS.
42
 * This policy does not immediately delete its children.  Instead, it marks them deactivated
43
 * and starts a timer for deletion.  If a subsequent address update restores the child, then it is
44
 * simply reactivated instead of built from scratch.  This is necessary because XDS can frequently
45
 * remove and then add back a server as machines are rebooted or repurposed for load management.
46
 *
47
 * <p>Note that this LB does not automatically reconnect children who go into IDLE status
48
 */
49
class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
50

51
  // 15 minutes is long enough for a reboot and the services to restart while not so long that
52
  // many children are waiting for cleanup.
53
  @VisibleForTesting
54
  public static final int DELAYED_CHILD_DELETION_TIME_MINUTES = 15;
55
  protected final SynchronizationContext syncContext;
56
  private final ScheduledExecutorService timeService;
57
  private final XdsLogger logger;
58

59
  ClusterManagerLoadBalancer(Helper helper) {
60
    super(helper);
1✔
61
    this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
1✔
62
    this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService");
1✔
63
    logger = XdsLogger.withLogId(
1✔
64
        InternalLogId.allocate("cluster_manager-lb", helper.getAuthority()));
1✔
65

66
    logger.log(XdsLogLevel.INFO, "Created");
1✔
67
  }
1✔
68

69
  @Override
70
  protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses,
71
      Object childConfig) {
72
    return resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build();
1✔
73
  }
74

75
  @Override
76
  protected Map<Object, ChildLbState> createChildLbMap(ResolvedAddresses resolvedAddresses) {
77
    ClusterManagerConfig config = (ClusterManagerConfig)
1✔
78
        resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
79
    Map<Object, ChildLbState> newChildPolicies = new HashMap<>();
1✔
80
    if (config != null) {
1✔
81
      for (Entry<String, PolicySelection> entry : config.childPolicies.entrySet()) {
1✔
82
        ChildLbState child = getChildLbState(entry.getKey());
1✔
83
        if (child == null) {
1✔
84
          child = new ClusterManagerLbState(entry.getKey(),
1✔
85
              entry.getValue().getProvider(), entry.getValue().getConfig(), getInitialPicker());
1✔
86
        }
87
        newChildPolicies.put(entry.getKey(), child);
1✔
88
      }
1✔
89
    }
90
    logger.log(
1✔
91
        XdsLogLevel.INFO,
92
        "Received cluster_manager lb config: child names={0}", newChildPolicies.keySet());
1✔
93
    return newChildPolicies;
1✔
94
  }
95

96
  /**
97
   * This is like the parent except that it doesn't shutdown the removed children since we want that
98
   * to be done by the timer.
99
   */
100
  @Override
101
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
102
    try {
103
      resolvingAddresses = true;
1✔
104

105
      // process resolvedAddresses to update children
106
      AcceptResolvedAddressRetVal acceptRetVal =
1✔
107
          acceptResolvedAddressesInternal(resolvedAddresses);
1✔
108
      if (!acceptRetVal.status.isOk()) {
1✔
109
        return acceptRetVal.status;
×
110
      }
111

112
      // Update the picker
113
      updateOverallBalancingState();
1✔
114

115
      return acceptRetVal.status;
1✔
116
    } finally {
117
      resolvingAddresses = false;
1✔
118
    }
119
  }
120

121
  @Override
122
  protected SubchannelPicker getSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {
123
    return new SubchannelPicker() {
1✔
124
      @Override
125
      public PickResult pickSubchannel(PickSubchannelArgs args) {
126
        String clusterName =
1✔
127
            args.getCallOptions().getOption(XdsNameResolver.CLUSTER_SELECTION_KEY);
1✔
128
        SubchannelPicker childPicker = childPickers.get(clusterName);
1✔
129
        if (childPicker == null) {
1✔
130
          return
1✔
131
              PickResult.withError(
1✔
132
                  Status.UNAVAILABLE.withDescription("CDS encountered error: unable to find "
1✔
133
                      + "available subchannel for cluster " + clusterName));
134
        }
135
        return childPicker.pickSubchannel(args);
1✔
136
      }
137

138
      @Override
139
      public String toString() {
140
        return MoreObjects.toStringHelper(this).add("pickers", childPickers).toString();
×
141
      }
142
    };
143
  }
144

145
  @Override
146
  public void handleNameResolutionError(Status error) {
147
    logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error);
1✔
148
    boolean gotoTransientFailure = true;
1✔
149
    for (ChildLbState state : getChildLbStates()) {
1✔
150
      if (!state.isDeactivated()) {
1✔
151
        gotoTransientFailure = false;
1✔
152
        handleNameResolutionError(state, error);
1✔
153
      }
154
    }
1✔
155
    if (gotoTransientFailure) {
1✔
156
      getHelper().updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error));
1✔
157
    }
158
  }
1✔
159

160
  @Override
161
  protected boolean reconnectOnIdle() {
162
    return false;
1✔
163
  }
164

165
  /**
166
   * This differs from the base class in the use of the deletion timer.  When it is deactivated,
167
   * rather than immediately calling shutdown it starts a timer.  If shutdown or reactivate
168
   * are called before the timer fires, the timer is canceled.  Otherwise, time timer calls shutdown
169
   * and removes the child from the petiole policy when it is triggered.
170
   */
171
  private class ClusterManagerLbState extends ChildLbState {
172
    @Nullable
173
    ScheduledHandle deletionTimer;
174

175
    public ClusterManagerLbState(Object key, LoadBalancerProvider policyProvider,
176
        Object childConfig, SubchannelPicker initialPicker) {
1✔
177
      super(key, policyProvider, childConfig, initialPicker);
1✔
178
    }
1✔
179

180
    @Override
181
    protected void shutdown() {
182
      if (deletionTimer != null && deletionTimer.isPending()) {
1✔
183
        deletionTimer.cancel();
1✔
184
      }
185
      super.shutdown();
1✔
186
    }
1✔
187

188
    @Override
189
    protected void reactivate(LoadBalancerProvider policyProvider) {
190
      if (deletionTimer != null && deletionTimer.isPending()) {
1✔
191
        deletionTimer.cancel();
1✔
192
        logger.log(XdsLogLevel.DEBUG, "Child balancer {0} reactivated", getKey());
1✔
193
      }
194

195
      super.reactivate(policyProvider);
1✔
196
    }
1✔
197

198
    @Override
199
    protected void deactivate() {
200
      if (isDeactivated()) {
1✔
201
        return;
×
202
      }
203

204
      class DeletionTask implements Runnable {
1✔
205

206
        @Override
207
        public void run() {
208
          shutdown();
1✔
209
          removeChild(getKey());
1✔
210
        }
1✔
211
      }
212

213
      deletionTimer =
1✔
214
          syncContext.schedule(
1✔
215
              new DeletionTask(),
216
              DELAYED_CHILD_DELETION_TIME_MINUTES,
217
              TimeUnit.MINUTES,
218
              timeService);
1✔
219
      setDeactivated();
1✔
220
      logger.log(XdsLogLevel.DEBUG, "Child balancer {0} deactivated", getKey());
1✔
221
    }
1✔
222

223
  }
224
}
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