• 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

95.85
/../util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java
1
/*
2
 * Copyright 2023 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 static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.ConnectivityState.CONNECTING;
22
import static io.grpc.ConnectivityState.IDLE;
23
import static io.grpc.ConnectivityState.READY;
24
import static io.grpc.ConnectivityState.SHUTDOWN;
25
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
26

27
import com.google.common.annotations.VisibleForTesting;
28
import com.google.common.collect.ImmutableList;
29
import com.google.common.collect.ImmutableMap;
30
import io.grpc.ConnectivityState;
31
import io.grpc.EquivalentAddressGroup;
32
import io.grpc.Internal;
33
import io.grpc.LoadBalancer;
34
import io.grpc.LoadBalancerProvider;
35
import io.grpc.Status;
36
import io.grpc.internal.PickFirstLoadBalancerProvider;
37
import java.net.SocketAddress;
38
import java.util.ArrayList;
39
import java.util.Arrays;
40
import java.util.Collection;
41
import java.util.Collections;
42
import java.util.HashMap;
43
import java.util.LinkedHashMap;
44
import java.util.List;
45
import java.util.Map;
46
import java.util.logging.Level;
47
import java.util.logging.Logger;
48
import javax.annotation.Nullable;
49

50
/**
51
 * A base load balancing policy for those policies which has multiple children such as
52
 * ClusterManager or the petiole policies.  For internal use only.
53
 */
54
@Internal
55
public abstract class MultiChildLoadBalancer extends LoadBalancer {
56

57
  private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
1✔
58
  private final Map<Object, ChildLbState> childLbStates = new LinkedHashMap<>();
1✔
59
  private final Helper helper;
60
  // Set to true if currently in the process of handling resolved addresses.
61
  protected boolean resolvingAddresses;
62

63
  protected final LoadBalancerProvider pickFirstLbProvider = new PickFirstLoadBalancerProvider();
1✔
64

65
  protected ConnectivityState currentConnectivityState;
66

67
  protected MultiChildLoadBalancer(Helper helper) {
1✔
68
    this.helper = checkNotNull(helper, "helper");
1✔
69
    logger.log(Level.FINE, "Created");
1✔
70
  }
1✔
71

72
  protected abstract SubchannelPicker getSubchannelPicker(
73
      Map<Object, SubchannelPicker> childPickers);
74

75
  protected SubchannelPicker getInitialPicker() {
76
    return EMPTY_PICKER;
1✔
77
  }
78

79
  protected SubchannelPicker getErrorPicker(Status error)  {
80
    return new FixedResultPicker(PickResult.withError(error));
1✔
81
  }
82

83
  /**
84
   * Generally, the only reason to override this is to expose it to a test of a LB in a different
85
   * package.
86
   */
87
  protected ImmutableMap<Object, ChildLbState> getImmutableChildMap() {
88
    return ImmutableMap.copyOf(childLbStates);
1✔
89
  }
90

91
  @VisibleForTesting
92
  protected Collection<ChildLbState> getChildLbStates() {
93
    return childLbStates.values();
1✔
94
  }
95

96
  /**
97
   * Generally, the only reason to override this is to expose it to a test of a LB in a
98
   * different package.
99
   */
100
  protected ChildLbState getChildLbState(Object key) {
101
    if (key == null) {
1✔
102
      return null;
×
103
    }
104
    if (key instanceof EquivalentAddressGroup) {
1✔
105
      key = new Endpoint((EquivalentAddressGroup) key);
1✔
106
    }
107
    return childLbStates.get(key);
1✔
108
  }
109

110
  /**
111
   * Generally, the only reason to override this is to expose it to a test of a LB in a different
112
   * package.
113
   */
114
  protected ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
115
    return getChildLbState(new Endpoint(eag));
1✔
116
  }
117

118
  /**
119
   * Override to utilize parsing of the policy configuration or alternative helper/lb generation.
120
   */
121
  protected Map<Object, ChildLbState> createChildLbMap(ResolvedAddresses resolvedAddresses) {
122
    Map<Object, ChildLbState> childLbMap = new HashMap<>();
1✔
123
    List<EquivalentAddressGroup> addresses = resolvedAddresses.getAddresses();
1✔
124
    for (EquivalentAddressGroup eag : addresses) {
1✔
125
      Endpoint endpoint = new Endpoint(eag); // keys need to be just addresses
1✔
126
      ChildLbState existingChildLbState = childLbStates.get(endpoint);
1✔
127
      if (existingChildLbState != null) {
1✔
128
        childLbMap.put(endpoint, existingChildLbState);
1✔
129
      } else {
130
        childLbMap.put(endpoint,
1✔
131
            createChildLbState(endpoint, null, getInitialPicker(), resolvedAddresses));
1✔
132
      }
133
    }
1✔
134
    return childLbMap;
1✔
135
  }
136

137
  /**
138
   * Override to create an instance of a subclass.
139
   */
140
  protected ChildLbState createChildLbState(Object key, Object policyConfig,
141
      SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) {
142
    return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker);
1✔
143
  }
144

145
  /**
146
   *   Override to completely replace the default logic or to do additional activities.
147
   */
148
  @Override
149
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
150
    try {
151
      resolvingAddresses = true;
1✔
152

153
      // process resolvedAddresses to update children
154
      AcceptResolvedAddressRetVal acceptRetVal =
1✔
155
          acceptResolvedAddressesInternal(resolvedAddresses);
1✔
156
      if (!acceptRetVal.status.isOk()) {
1✔
157
        return acceptRetVal.status;
1✔
158
      }
159

160
      // Update the picker and our connectivity state
161
      updateOverallBalancingState();
1✔
162

163
      // shutdown removed children
164
      shutdownRemoved(acceptRetVal.removedChildren);
1✔
165
      return acceptRetVal.status;
1✔
166
    } finally {
167
      resolvingAddresses = false;
1✔
168
    }
169
  }
170

171
  /**
172
   * Override this if your keys are not of type Endpoint.
173
   * @param key Key to identify the ChildLbState
174
   * @param resolvedAddresses list of addresses which include attributes
175
   * @param childConfig a load balancing policy config. This field is optional.
176
   * @return a fully loaded ResolvedAddresses object for the specified key
177
   */
178
  protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses,
179
      Object childConfig) {
180
    Endpoint endpointKey;
181
    if (key instanceof EquivalentAddressGroup) {
1✔
182
      endpointKey = new Endpoint((EquivalentAddressGroup) key);
×
183
    } else {
184
      checkArgument(key instanceof Endpoint, "key is wrong type");
1✔
185
      endpointKey = (Endpoint) key;
1✔
186
    }
187

188
    // Retrieve the non-stripped version
189
    EquivalentAddressGroup eagToUse = null;
1✔
190
    for (EquivalentAddressGroup currEag : resolvedAddresses.getAddresses()) {
1✔
191
      if (endpointKey.equals(new Endpoint(currEag))) {
1✔
192
        eagToUse = currEag;
1✔
193
        break;
1✔
194
      }
195
    }
1✔
196

197
    checkNotNull(eagToUse, key + " no longer present in load balancer children");
1✔
198

199
    return resolvedAddresses.toBuilder()
1✔
200
        .setAddresses(Collections.singletonList(eagToUse))
1✔
201
        .setLoadBalancingPolicyConfig(childConfig)
1✔
202
        .build();
1✔
203
  }
204

205
  /**
206
   *   This does the work to update the child map and calculate which children have been removed.
207
   *   You must call {@link #updateOverallBalancingState} to update the picker
208
   *   and call {@link #shutdownRemoved(List)} to shutdown the endpoints that have been removed.
209
    */
210
  protected AcceptResolvedAddressRetVal acceptResolvedAddressesInternal(
211
      ResolvedAddresses resolvedAddresses) {
212
    logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
1✔
213
    Map<Object, ChildLbState> newChildren = createChildLbMap(resolvedAddresses);
1✔
214

215
    if (newChildren.isEmpty()) {
1✔
216
      Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
217
          "NameResolver returned no usable address. " + resolvedAddresses);
218
      handleNameResolutionError(unavailableStatus);
1✔
219
      return new AcceptResolvedAddressRetVal(unavailableStatus, null);
1✔
220
    }
221

222
    // Do adds and updates
223
    for (Map.Entry<Object, ChildLbState> entry : newChildren.entrySet()) {
1✔
224
      final Object key = entry.getKey();
1✔
225
      LoadBalancerProvider childPolicyProvider = entry.getValue().getPolicyProvider();
1✔
226
      Object childConfig = entry.getValue().getConfig();
1✔
227
      if (!childLbStates.containsKey(key)) {
1✔
228
        childLbStates.put(key, entry.getValue());
1✔
229
      } else {
230
        // Reuse the existing one
231
        ChildLbState existingChildLbState = childLbStates.get(key);
1✔
232
        if (existingChildLbState.isDeactivated() && reactivateChildOnReuse()) {
1✔
233
          existingChildLbState.reactivate(childPolicyProvider);
1✔
234
        }
235
      }
236

237
      ChildLbState childLbState = childLbStates.get(key);
1✔
238
      ResolvedAddresses childAddresses = getChildAddresses(key, resolvedAddresses, childConfig);
1✔
239
      childLbStates.get(key).setResolvedAddresses(childAddresses); // update child
1✔
240
      if (!childLbState.deactivated) {
1✔
241
        childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB
1✔
242
      }
243
    }
1✔
244

245
    List<ChildLbState> removedChildren = new ArrayList<>();
1✔
246
    // Do removals
247
    for (Object key : ImmutableList.copyOf(childLbStates.keySet())) {
1✔
248
      if (!newChildren.containsKey(key)) {
1✔
249
        ChildLbState childLbState = childLbStates.get(key);
1✔
250
        childLbState.deactivate();
1✔
251
        removedChildren.add(childLbState);
1✔
252
      }
253
    }
1✔
254

255
    return new AcceptResolvedAddressRetVal(Status.OK, removedChildren);
1✔
256
  }
257

258
  protected void shutdownRemoved(List<ChildLbState> removedChildren) {
259
    // Do shutdowns after updating picker to reduce the chance of failing an RPC by picking a
260
    // subchannel that has been shutdown.
261
    for (ChildLbState childLbState : removedChildren) {
1✔
262
      childLbState.shutdown();
1✔
263
    }
1✔
264
  }
1✔
265

266
  @Override
267
  public void handleNameResolutionError(Status error) {
268
    if (currentConnectivityState != READY)  {
1✔
269
      helper.updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error));
1✔
270
    }
271
  }
1✔
272

273
  protected void handleNameResolutionError(ChildLbState child, Status error) {
274
    child.lb.handleNameResolutionError(error);
1✔
275
  }
1✔
276

277
  /**
278
   * If true, then when a subchannel state changes to idle, the corresponding child will
279
   * have requestConnection called on its LB.  Also causes the PickFirstLB to be created when
280
   * the child is created or reused.
281
   */
282
  protected boolean reconnectOnIdle() {
283
    return true;
1✔
284
  }
285

286
  /**
287
   * If true, then when {@link #acceptResolvedAddresses} sees a key that was already part of the
288
   * child map which is deactivated, it will call reactivate on the child.
289
   * If false, it will leave it deactivated.
290
   */
291
  protected boolean reactivateChildOnReuse() {
292
    return true;
1✔
293
  }
294

295
  @Override
296
  public void shutdown() {
297
    logger.log(Level.INFO, "Shutdown");
1✔
298
    for (ChildLbState state : childLbStates.values()) {
1✔
299
      state.shutdown();
1✔
300
    }
1✔
301
    childLbStates.clear();
1✔
302
  }
1✔
303

304
  protected void updateOverallBalancingState() {
305
    ConnectivityState overallState = null;
1✔
306
    final Map<Object, SubchannelPicker> childPickers = new HashMap<>();
1✔
307
    for (ChildLbState childLbState : getChildLbStates()) {
1✔
308
      if (childLbState.deactivated) {
1✔
309
        continue;
1✔
310
      }
311
      childPickers.put(childLbState.key, childLbState.currentPicker);
1✔
312
      overallState = aggregateState(overallState, childLbState.currentState);
1✔
313
    }
1✔
314

315
    if (overallState != null) {
1✔
316
      helper.updateBalancingState(overallState, getSubchannelPicker(childPickers));
1✔
317
      currentConnectivityState = overallState;
1✔
318
    }
319
  }
1✔
320

321
  @Nullable
322
  protected static ConnectivityState aggregateState(
323
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
324
    if (overallState == null) {
1✔
325
      return childState;
1✔
326
    }
327
    if (overallState == READY || childState == READY) {
1✔
328
      return READY;
1✔
329
    }
330
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
331
      return CONNECTING;
1✔
332
    }
333
    if (overallState == IDLE || childState == IDLE) {
1✔
334
      return IDLE;
×
335
    }
336
    return overallState;
1✔
337
  }
338

339
  protected Helper getHelper() {
340
    return helper;
1✔
341
  }
342

343
  protected void removeChild(Object key) {
344
    childLbStates.remove(key);
1✔
345
  }
1✔
346

347
  /**
348
   * Filters out non-ready and deactivated child load balancers (subchannels).
349
   */
350
  protected List<ChildLbState> getReadyChildren() {
351
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
352
    for (ChildLbState child : getChildLbStates()) {
1✔
353
      if (!child.isDeactivated() && child.getCurrentState() == READY) {
1✔
354
        activeChildren.add(child);
1✔
355
      }
356
    }
1✔
357
    return activeChildren;
1✔
358
  }
359

360
  /**
361
   * This represents the state of load balancer children.  Each endpoint (represented by an
362
   * EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
363
   * define a GracefulSwitchLoadBalancer.  When the GracefulSwitchLoadBalancer is activated, a
364
   * single PickFirstLoadBalancer will be created which will then create a subchannel and start
365
   * trying to connect to it.
366
   *
367
   * <p>A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
368
   * petiole policy above and the PickFirstLoadBalancer's helper below.
369
   *
370
   * <p>If you wish to store additional state information related to each subchannel, then extend
371
   * this class.
372
   */
373
  public class ChildLbState {
374
    private final Object key;
375
    private ResolvedAddresses resolvedAddresses;
376
    private final Object config;
377

378
    private final GracefulSwitchLoadBalancer lb;
379
    private final LoadBalancerProvider policyProvider;
380
    private ConnectivityState currentState;
381
    private SubchannelPicker currentPicker;
382
    private boolean deactivated;
383

384
    public ChildLbState(Object key, LoadBalancerProvider policyProvider, Object childConfig,
385
        SubchannelPicker initialPicker) {
386
      this(key, policyProvider, childConfig, initialPicker, null, false);
1✔
387
    }
1✔
388

389
    public ChildLbState(Object key, LoadBalancerProvider policyProvider, Object childConfig,
390
          SubchannelPicker initialPicker, ResolvedAddresses resolvedAddrs, boolean deactivated) {
1✔
391
      this.key = key;
1✔
392
      this.policyProvider = policyProvider;
1✔
393
      this.deactivated = deactivated;
1✔
394
      this.currentPicker = initialPicker;
1✔
395
      this.config = childConfig;
1✔
396
      this.lb = new GracefulSwitchLoadBalancer(new ChildLbStateHelper());
1✔
397
      this.currentState = deactivated ? IDLE : CONNECTING;
1✔
398
      this.resolvedAddresses = resolvedAddrs;
1✔
399
      if (!deactivated) {
1✔
400
        lb.switchTo(policyProvider);
1✔
401
      }
402
    }
1✔
403

404
    @Override
405
    public String toString() {
406
      return "Address = " + key
1✔
407
          + ", state = " + currentState
408
          + ", picker type: " + currentPicker.getClass()
1✔
409
          + ", lb: " + lb.delegate().getClass()
1✔
410
          + (deactivated ? ", deactivated" : "");
1✔
411
    }
412

413
    public Object getKey() {
414
      return key;
1✔
415
    }
416

417
    Object getConfig() {
418
      return config;
1✔
419
    }
420

421
    protected GracefulSwitchLoadBalancer getLb() {
422
      return lb;
1✔
423
    }
424

425
    public LoadBalancerProvider getPolicyProvider() {
426
      return policyProvider;
1✔
427
    }
428

429
    protected Subchannel getSubchannels(PickSubchannelArgs args) {
430
      if (getCurrentPicker() == null) {
1✔
431
        return null;
×
432
      }
433
      return getCurrentPicker().pickSubchannel(args).getSubchannel();
1✔
434
    }
435

436
    public ConnectivityState getCurrentState() {
437
      return currentState;
1✔
438
    }
439

440
    public SubchannelPicker getCurrentPicker() {
441
      return currentPicker;
1✔
442
    }
443

444
    public EquivalentAddressGroup getEag() {
445
      if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
1✔
446
        return null;
×
447
      }
448
      return resolvedAddresses.getAddresses().get(0);
1✔
449
    }
450

451
    public boolean isDeactivated() {
452
      return deactivated;
1✔
453
    }
454

455
    protected void setDeactivated() {
456
      deactivated = true;
1✔
457
    }
1✔
458

459
    protected void markReactivated() {
460
      deactivated = false;
1✔
461
    }
1✔
462

463
    protected void setResolvedAddresses(ResolvedAddresses newAddresses) {
464
      checkNotNull(newAddresses, "Missing address list for child");
1✔
465
      resolvedAddresses = newAddresses;
1✔
466
    }
1✔
467

468
    /**
469
     * The default implementation. This not only marks the lb policy as not active, it also removes
470
     * this child from the map of children maintained by the petiole policy.
471
     *
472
     * <p>Note that this does not explicitly shutdown this child.  That will generally be done by
473
     * acceptResolvedAddresses on the LB, but can also be handled by an override such as is done
474
     * in <a href=" https://github.com/grpc/grpc-java/blob/master/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java">ClusterManagerLoadBalancer</a>.
475
     *
476
     * <p>If you plan to reactivate, you will probably want to override this to not call
477
     * childLbStates.remove() and handle that cleanup another way.
478
     */
479
    protected void deactivate() {
480
      if (deactivated) {
1✔
481
        return;
×
482
      }
483

484
      childLbStates.remove(key); // This means it can't be reactivated again
1✔
485
      deactivated = true;
1✔
486
      logger.log(Level.FINE, "Child balancer {0} deactivated", key);
1✔
487
    }
1✔
488

489
    /**
490
     * This base implementation does nothing but reset the flag.  If you really want to both
491
     * deactivate and reactivate you should override them both.
492
     */
493
    protected void reactivate(LoadBalancerProvider policyProvider) {
494
      deactivated = false;
1✔
495
    }
1✔
496

497
    protected void shutdown() {
498
      lb.shutdown();
1✔
499
      this.currentState = SHUTDOWN;
1✔
500
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
501
    }
1✔
502

503
    /**
504
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
505
     * petiole policy above and the PickFirstLoadBalancer's helper below.
506
     *
507
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
508
     * simple forwarding.
509
     */
510
    protected ResolvedAddresses getResolvedAddresses() {
511
      return resolvedAddresses;
1✔
512
    }
513

514
    private final class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
515

516
      @Override
517
      public void updateBalancingState(final ConnectivityState newState,
518
          final SubchannelPicker newPicker) {
519
        // If we are already in the process of resolving addresses, the overall balancing state
520
        // will be updated at the end of it, and we don't need to trigger that update here.
521
        if (!childLbStates.containsKey(key)) {
1✔
522
          return;
1✔
523
        }
524
        // Subchannel picker and state are saved, but will only be propagated to the channel
525
        // when the child instance exits deactivated state.
526
        currentState = newState;
1✔
527
        currentPicker = newPicker;
1✔
528
        if (!deactivated && !resolvingAddresses) {
1✔
529
          if (newState == IDLE && reconnectOnIdle()) {
1✔
530
            lb.requestConnection();
1✔
531
          }
532
          updateOverallBalancingState();
1✔
533
        }
534
      }
1✔
535

536
      @Override
537
      protected Helper delegate() {
538
        return helper;
1✔
539
      }
540
    }
541
  }
542

543
  /**
544
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
545
   * Ignores the attributes, orders the addresses in a deterministic manner and converts each
546
   * address into a string for easy comparison.  Also caches the hashcode.
547
   * Is used as a key for ChildLbState for most load balancers (ClusterManagerLB uses a String).
548
   */
549
  protected static class Endpoint {
550
    final String[] addrs;
551
    final int hashCode;
552

553
    public Endpoint(EquivalentAddressGroup eag) {
1✔
554
      checkNotNull(eag, "eag");
1✔
555

556
      addrs = new String[eag.getAddresses().size()];
1✔
557
      int i = 0;
1✔
558
      for (SocketAddress address : eag.getAddresses()) {
1✔
559
        addrs[i] = address.toString();
1✔
560
      }
1✔
561
      Arrays.sort(addrs);
1✔
562

563
      hashCode = Arrays.hashCode(addrs);
1✔
564
    }
1✔
565

566
    @Override
567
    public int hashCode() {
568
      return hashCode;
1✔
569
    }
570

571
    @Override
572
    public boolean equals(Object other) {
573
      if (this == other) {
1✔
574
        return true;
×
575
      }
576
      if (other == null) {
1✔
577
        return false;
×
578
      }
579

580
      if (!(other instanceof Endpoint)) {
1✔
581
        return false;
×
582
      }
583
      Endpoint o = (Endpoint) other;
1✔
584
      if (o.hashCode != hashCode || o.addrs.length != addrs.length) {
1✔
585
        return false;
1✔
586
      }
587

588
      return Arrays.equals(o.addrs, this.addrs);
1✔
589
    }
590

591
    @Override
592
    public String toString() {
593
      return Arrays.toString(addrs);
1✔
594
    }
595
  }
596

597
  protected static class AcceptResolvedAddressRetVal {
598
    public final Status status;
599
    public final List<ChildLbState> removedChildren;
600

601
    public AcceptResolvedAddressRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
602
      this.status = status;
1✔
603
      this.removedChildren = removedChildren;
1✔
604
    }
1✔
605
  }
606
}
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