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

grpc / grpc-java / #18874

26 Oct 2023 06:34PM CUT coverage: 88.249% (-0.009%) from 88.258%
#18874

push

github-actions

web-flow
xds: Log ORCA UNIMPLEMENTED error to subchannel logger (#10625) (#10631)

Logging to the static instance would result in application logs filling
up if the Orca service is not available.
We'd like to have the logging on the subchannelLogger, so we make it
visible on demand.

Also succeed Orca logging test if log message present. Using
contains over containsExactly seems more reasonable.

Co-authored-by: Yannick Epstein <yannick.epstein@gmail.com>

30294 of 34328 relevant lines covered (88.25%)

0.88 hits per line

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

94.68
/../xds/src/main/java/io/grpc/xds/orca/OrcaOobUtil.java
1
/*
2
 * Copyright 2019 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.orca;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static com.google.common.base.Preconditions.checkState;
21
import static io.grpc.ConnectivityState.IDLE;
22
import static io.grpc.ConnectivityState.READY;
23

24
import com.github.xds.data.orca.v3.OrcaLoadReport;
25
import com.github.xds.service.orca.v3.OpenRcaServiceGrpc;
26
import com.github.xds.service.orca.v3.OrcaLoadReportRequest;
27
import com.google.common.annotations.VisibleForTesting;
28
import com.google.common.base.MoreObjects;
29
import com.google.common.base.Objects;
30
import com.google.common.base.Stopwatch;
31
import com.google.common.base.Supplier;
32
import com.google.protobuf.util.Durations;
33
import io.grpc.Attributes;
34
import io.grpc.CallOptions;
35
import io.grpc.Channel;
36
import io.grpc.ChannelLogger;
37
import io.grpc.ChannelLogger.ChannelLogLevel;
38
import io.grpc.ClientCall;
39
import io.grpc.ConnectivityStateInfo;
40
import io.grpc.ExperimentalApi;
41
import io.grpc.LoadBalancer;
42
import io.grpc.LoadBalancer.CreateSubchannelArgs;
43
import io.grpc.LoadBalancer.Helper;
44
import io.grpc.LoadBalancer.Subchannel;
45
import io.grpc.LoadBalancer.SubchannelStateListener;
46
import io.grpc.Metadata;
47
import io.grpc.Status;
48
import io.grpc.Status.Code;
49
import io.grpc.SynchronizationContext;
50
import io.grpc.SynchronizationContext.ScheduledHandle;
51
import io.grpc.internal.BackoffPolicy;
52
import io.grpc.internal.ExponentialBackoffPolicy;
53
import io.grpc.internal.GrpcUtil;
54
import io.grpc.services.MetricReport;
55
import io.grpc.util.ForwardingLoadBalancerHelper;
56
import io.grpc.util.ForwardingSubchannel;
57
import java.util.HashMap;
58
import java.util.Map;
59
import java.util.concurrent.ScheduledExecutorService;
60
import java.util.concurrent.TimeUnit;
61
import javax.annotation.Nullable;
62

63
/**
64
 * Utility class that provides method for {@link LoadBalancer} to install listeners to receive
65
 * out-of-band backend metrics in the format of Open Request Cost Aggregation (ORCA).
66
 */
67
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9129")
68
public final class OrcaOobUtil {
69

70
  private OrcaOobUtil() {}
71

72
  /**
73
   * Creates a new {@link io.grpc.LoadBalancer.Helper} with provided
74
   * {@link OrcaOobReportListener} installed
75
   * to receive callback when an out-of-band ORCA report is received.
76
   *
77
   * <p>Example usages:
78
   *
79
   * <ul>
80
   *   <li> Leaf policy (e.g., WRR policy)
81
   *     <pre>
82
   *       {@code
83
   *       class WrrLoadbalancer extends LoadBalancer {
84
   *         private final Helper originHelper;  // the original Helper
85
   *
86
   *         public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
87
   *           // listener implements the logic for WRR's usage of backend metrics.
88
   *           OrcaReportingHelper orcaHelper =
89
   *               OrcaOobUtil.newOrcaReportingHelper(originHelper);
90
   *           Subchannel subchannel =
91
   *               orcaHelper.createSubchannel(CreateSubchannelArgs.newBuilder()...);
92
   *           OrcaOobUtil.setListener(
93
   *              subchannel,
94
   *              listener,
95
   *              OrcaRerportingConfig.newBuilder().setReportInterval(30, SECOND).build());
96
   *           ...
97
   *         }
98
   *       }
99
   *       }
100
   *     </pre>
101
   *   </li>
102
   *   <li> Delegating policy doing per-child-policy aggregation
103
   *     <pre>
104
   *       {@code
105
   *       class XdsLoadBalancer extends LoadBalancer {
106
   *         private final Helper orcaHelper;  // the original Helper
107
   *
108
   *         public XdsLoadBalancer(LoadBalancer.Helper helper) {
109
   *           this.orcaHelper = OrcaUtil.newOrcaReportingHelper(helper);
110
   *         }
111
   *         private void createChildPolicy(
112
   *             Locality locality, LoadBalancerProvider childPolicyProvider) {
113
   *           // Each Locality has a child policy, and the parent does per-locality aggregation by
114
   *           // summing everything up.
115
   *
116
   *           // Create an OrcaReportingHelperWrapper for each Locality.
117
   *           // listener implements the logic for locality-level backend metric aggregation.
118
   *           LoadBalancer childLb = childPolicyProvider.newLoadBalancer(
119
   *             new ForwardingLoadBalancerHelper() {
120
   *               public Subchannel createSubchannel(CreateSubchannelArgs args) {
121
   *                 Subchannel subchannel = super.createSubchannel(args);
122
   *                 OrcaOobUtil.setListener(subchannel, listener,
123
   *                 OrcaReportingConfig.newBuilder().setReportInterval(30, SECOND).build());
124
   *                 return subchannel;
125
   *               }
126
   *               public LoadBalancer.Helper delegate() {
127
   *                 return orcaHelper;
128
   *               }
129
   *             });
130
   *         }
131
   *       }
132
   *       }
133
   *     </pre>
134
   *   </li>
135
   * </ul>
136
   *
137
   * @param delegate the delegate helper that provides essentials for establishing subchannels to
138
   *     backends.
139
   */
140
  public static LoadBalancer.Helper newOrcaReportingHelper(LoadBalancer.Helper delegate) {
141
    return newOrcaReportingHelper(
1✔
142
        delegate,
143
        new ExponentialBackoffPolicy.Provider(),
144
        GrpcUtil.STOPWATCH_SUPPLIER);
145
  }
146

147
  @VisibleForTesting
148
  static LoadBalancer.Helper newOrcaReportingHelper(
149
      LoadBalancer.Helper delegate,
150
      BackoffPolicy.Provider backoffPolicyProvider,
151
      Supplier<Stopwatch> stopwatchSupplier) {
152
    return new OrcaReportingHelper(delegate, backoffPolicyProvider, stopwatchSupplier);
1✔
153
  }
154

155
  /**
156
   * The listener interface for receiving out-of-band ORCA reports from backends. The class that is
157
   * interested in processing backend cost metrics implements this interface, and the object created
158
   * with that class is registered with a component, using methods in {@link OrcaPerRequestUtil}.
159
   * When an ORCA report is received, that object's {@code onLoadReport} method is invoked.
160
   */
161
  public interface OrcaOobReportListener {
162

163
    /**
164
     * Invoked when an out-of-band ORCA report is received.
165
     *
166
     * <p>Note this callback will be invoked from the {@link SynchronizationContext} of the
167
     * delegated helper, implementations should not block.
168
     *
169
     * @param report load report in the format of grpc {@link MetricReport}.
170
     */
171
    void onLoadReport(MetricReport report);
172
  }
173

174
  static final Attributes.Key<SubchannelImpl> ORCA_REPORTING_STATE_KEY =
1✔
175
      Attributes.Key.create("internal-orca-reporting-state");
1✔
176

177
  /**
178
   *  Update {@link OrcaOobReportListener} to receive Out-of-Band metrics report for the
179
   *  particular subchannel connection, and set the configuration of receiving ORCA reports,
180
   *  such as the interval of receiving reports. Set listener to null to remove listener, and the
181
   *  config will have no effect.
182
   *
183
   * <p>This method needs to be called from the SynchronizationContext returned by the wrapped
184
   * helper's {@link Helper#getSynchronizationContext()}.
185
   *
186
   * <p>Each load balancing policy must call this method to configure the backend load reporting.
187
   * Otherwise, it will not receive ORCA reports.
188
   *
189
   * <p>If multiple load balancing policies configure reporting with different intervals, reports
190
   * come with the minimum of those intervals.
191
   *
192
   * @param subchannel the server connected by this subchannel to receive the metrics.
193
   *
194
   * @param listener the callback upon receiving backend metrics from the Out-Of-Band stream.
195
   *                 Setting to null to removes the listener from the subchannel.
196
   *
197
   * @param config the configuration to be set. It has no effect when listener is null.
198
   *
199
   */
200
  public static void setListener(Subchannel subchannel, OrcaOobReportListener listener,
201
                                 OrcaReportingConfig config) {
202
    SubchannelImpl orcaSubchannel = subchannel.getAttributes().get(ORCA_REPORTING_STATE_KEY);
1✔
203
    if (orcaSubchannel == null) {
1✔
204
      throw new IllegalArgumentException("Subchannel does not have orca Out-Of-Band stream enabled."
1✔
205
          + " Try to use a subchannel created by OrcaOobUtil.OrcaHelper.");
206
    }
207
    orcaSubchannel.orcaState.setListener(orcaSubchannel, listener, config);
1✔
208
  }
1✔
209

210
  /**
211
   * An {@link OrcaReportingHelper} wraps a delegated {@link LoadBalancer.Helper} with additional
212
   * functionality to manage RPCs for out-of-band ORCA reporting for each backend it establishes
213
   * connection to. Subchannels created through it will retrieve ORCA load reports if the server
214
   * supports it.
215
   */
216
  static final class OrcaReportingHelper extends ForwardingLoadBalancerHelper {
217
    private final LoadBalancer.Helper delegate;
218
    private final SynchronizationContext syncContext;
219
    private final BackoffPolicy.Provider backoffPolicyProvider;
220
    private final Supplier<Stopwatch> stopwatchSupplier;
221

222
    OrcaReportingHelper(
223
        LoadBalancer.Helper delegate,
224
        BackoffPolicy.Provider backoffPolicyProvider,
225
        Supplier<Stopwatch> stopwatchSupplier) {
1✔
226
      this.delegate = checkNotNull(delegate, "delegate");
1✔
227
      this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
1✔
228
      this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
1✔
229
      syncContext = checkNotNull(delegate.getSynchronizationContext(), "syncContext");
1✔
230
    }
1✔
231

232
    @Override
233
    protected Helper delegate() {
234
      return delegate;
1✔
235
    }
236

237
    @Override
238
    public Subchannel createSubchannel(CreateSubchannelArgs args) {
239
      syncContext.throwIfNotInThisSynchronizationContext();
1✔
240
      Subchannel subchannel = super.createSubchannel(args);
1✔
241
      SubchannelImpl orcaSubchannel = subchannel.getAttributes().get(ORCA_REPORTING_STATE_KEY);
1✔
242
      OrcaReportingState orcaState;
243
      if (orcaSubchannel == null) {
1✔
244
        // Only the first load balancing policy requesting ORCA reports instantiates an
245
        // OrcaReportingState.
246
        orcaState = new OrcaReportingState(syncContext, delegate().getScheduledExecutorService());
1✔
247
      } else {
248
        orcaState = orcaSubchannel.orcaState;
1✔
249
      }
250
      return new SubchannelImpl(subchannel, orcaState);
1✔
251
    }
252

253
    /**
254
     * An {@link OrcaReportingState} is a client of ORCA service running on a single backend.
255
     *
256
     * <p>All methods are run from {@code syncContext}.
257
     */
258
    private final class OrcaReportingState implements SubchannelStateListener {
259

260
      private final SynchronizationContext syncContext;
261
      private final ScheduledExecutorService timeService;
262
      private final Map<OrcaOobReportListener, OrcaReportingConfig> configs = new HashMap<>();
1✔
263
      @Nullable private Subchannel subchannel;
264
      @Nullable private ChannelLogger subchannelLogger;
265
      @Nullable
266
      private SubchannelStateListener stateListener;
267
      @Nullable private BackoffPolicy backoffPolicy;
268
      @Nullable private OrcaReportingStream orcaRpc;
269
      @Nullable private ScheduledHandle retryTimer;
270
      @Nullable private OrcaReportingConfig overallConfig;
271
      private final Runnable retryTask =
1✔
272
          new Runnable() {
1✔
273
            @Override
274
            public void run() {
275
              startRpc();
1✔
276
            }
1✔
277
          };
278
      private ConnectivityStateInfo state = ConnectivityStateInfo.forNonError(IDLE);
1✔
279
      // True if server returned UNIMPLEMENTED.
280
      private boolean disabled;
281
      private boolean started;
282

283
      OrcaReportingState(
284
          SynchronizationContext syncContext,
285
          ScheduledExecutorService timeService) {
1✔
286
        this.syncContext = checkNotNull(syncContext, "syncContext");
1✔
287
        this.timeService = checkNotNull(timeService, "timeService");
1✔
288
      }
1✔
289

290
      void init(Subchannel subchannel, SubchannelStateListener stateListener) {
291
        checkState(this.subchannel == null, "init() already called");
1✔
292
        this.subchannel = checkNotNull(subchannel, "subchannel");
1✔
293
        this.subchannelLogger = checkNotNull(subchannel.getChannelLogger(), "subchannelLogger");
1✔
294
        this.stateListener = checkNotNull(stateListener, "stateListener");
1✔
295
        started = true;
1✔
296
      }
1✔
297

298
      void setListener(SubchannelImpl orcaSubchannel, OrcaOobReportListener listener,
299
                       OrcaReportingConfig config) {
300
        syncContext.execute(new Runnable() {
1✔
301
          @Override
302
          public void run() {
303
            OrcaOobReportListener oldListener = orcaSubchannel.reportListener;
1✔
304
            if (oldListener != null) {
1✔
305
              configs.remove(oldListener);
1✔
306
            }
307
            if (listener != null) {
1✔
308
              configs.put(listener, config);
1✔
309
            }
310
            orcaSubchannel.reportListener = listener;
1✔
311
            setReportingConfig(config);
1✔
312
          }
1✔
313
        });
314
      }
1✔
315

316
      private void setReportingConfig(OrcaReportingConfig config) {
317
        boolean reconfigured = false;
1✔
318
        // Real reporting interval is the minimum of intervals requested by all participating
319
        // helpers.
320
        if (configs.isEmpty()) {
1✔
321
          overallConfig = null;
1✔
322
          reconfigured = true;
1✔
323
        } else if (overallConfig == null) {
1✔
324
          overallConfig = config.toBuilder().build();
1✔
325
          reconfigured = true;
1✔
326
        } else {
327
          long minInterval = Long.MAX_VALUE;
1✔
328
          for (OrcaReportingConfig c : configs.values()) {
1✔
329
            if (c.getReportIntervalNanos() < minInterval) {
1✔
330
              minInterval = c.getReportIntervalNanos();
1✔
331
            }
332
          }
1✔
333
          if (overallConfig.getReportIntervalNanos() != minInterval) {
1✔
334
            overallConfig = overallConfig.toBuilder()
1✔
335
                .setReportInterval(minInterval, TimeUnit.NANOSECONDS).build();
1✔
336
            reconfigured = true;
1✔
337
          }
338
        }
339
        if (reconfigured) {
1✔
340
          stopRpc("ORCA reporting reconfigured");
1✔
341
          adjustOrcaReporting();
1✔
342
        }
343
      }
1✔
344

345
      @Override
346
      public void onSubchannelState(ConnectivityStateInfo newState) {
347
        if (Objects.equal(state.getState(), READY) && !Objects.equal(newState.getState(), READY)) {
1✔
348
          // A connection was lost.  We will reset disabled flag because ORCA service
349
          // may be available on the new connection.
350
          disabled = false;
1✔
351
        }
352
        state = newState;
1✔
353
        adjustOrcaReporting();
1✔
354
        // Propagate subchannel state update to downstream listeners.
355
        stateListener.onSubchannelState(newState);
1✔
356
      }
1✔
357

358
      void adjustOrcaReporting() {
359
        if (!disabled && overallConfig != null && Objects.equal(state.getState(), READY)) {
1✔
360
          if (orcaRpc == null && !isRetryTimerPending()) {
1✔
361
            startRpc();
1✔
362
          }
363
        } else {
364
          stopRpc("Client stops ORCA reporting");
1✔
365
          backoffPolicy = null;
1✔
366
        }
367
      }
1✔
368

369
      void startRpc() {
370
        checkState(orcaRpc == null, "previous orca reporting RPC has not been cleaned up");
1✔
371
        checkState(subchannel != null, "init() not called");
1✔
372
        subchannelLogger.log(
1✔
373
            ChannelLogLevel.DEBUG, "Starting ORCA reporting for {0}", subchannel.getAllAddresses());
1✔
374
        orcaRpc = new OrcaReportingStream(subchannel.asChannel(), stopwatchSupplier.get());
1✔
375
        orcaRpc.start();
1✔
376
      }
1✔
377

378
      void stopRpc(String msg) {
379
        if (orcaRpc != null) {
1✔
380
          orcaRpc.cancel(msg);
1✔
381
          orcaRpc = null;
1✔
382
        }
383
        if (retryTimer != null) {
1✔
384
          retryTimer.cancel();
1✔
385
          retryTimer = null;
1✔
386
        }
387
      }
1✔
388

389
      boolean isRetryTimerPending() {
390
        return retryTimer != null && retryTimer.isPending();
1✔
391
      }
392

393
      @Override
394
      public String toString() {
395
        return MoreObjects.toStringHelper(this)
×
396
            .add("disabled", disabled)
×
397
            .add("orcaRpc", orcaRpc)
×
398
            .add("reportingConfig", overallConfig)
×
399
            .add("connectivityState", state)
×
400
            .toString();
×
401
      }
402

403
      private class OrcaReportingStream extends ClientCall.Listener<OrcaLoadReport> {
404

405
        private final ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call;
406
        private final Stopwatch stopwatch;
407
        private boolean callHasResponded;
408

409
        OrcaReportingStream(Channel channel, Stopwatch stopwatch) {
1✔
410
          call =
1✔
411
              checkNotNull(channel, "channel")
1✔
412
                  .newCall(OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
1✔
413
          this.stopwatch = checkNotNull(stopwatch, "stopwatch");
1✔
414
        }
1✔
415

416
        void start() {
417
          stopwatch.reset().start();
1✔
418
          call.start(this, new Metadata());
1✔
419
          call.sendMessage(
1✔
420
              OrcaLoadReportRequest.newBuilder()
1✔
421
                  .setReportInterval(Durations.fromNanos(overallConfig.getReportIntervalNanos()))
1✔
422
                  .build());
1✔
423
          call.halfClose();
1✔
424
          call.request(1);
1✔
425
        }
1✔
426

427
        @Override
428
        public void onMessage(final OrcaLoadReport response) {
429
          syncContext.execute(
1✔
430
              new Runnable() {
1✔
431
                @Override
432
                public void run() {
433
                  if (orcaRpc == OrcaReportingStream.this) {
1✔
434
                    handleResponse(response);
1✔
435
                  }
436
                }
1✔
437
              });
438
        }
1✔
439

440
        @Override
441
        public void onClose(final Status status, Metadata trailers) {
442
          syncContext.execute(
1✔
443
              new Runnable() {
1✔
444
                @Override
445
                public void run() {
446
                  if (orcaRpc == OrcaReportingStream.this) {
1✔
447
                    orcaRpc = null;
1✔
448
                    handleStreamClosed(status);
1✔
449
                  }
450
                }
1✔
451
              });
452
        }
1✔
453

454
        void handleResponse(OrcaLoadReport response) {
455
          callHasResponded = true;
1✔
456
          backoffPolicy = null;
1✔
457
          subchannelLogger.log(ChannelLogLevel.DEBUG, "Received an ORCA report: {0}", response);
1✔
458
          MetricReport metricReport = OrcaPerRequestUtil.fromOrcaLoadReport(response);
1✔
459
          for (OrcaOobReportListener listener : configs.keySet()) {
1✔
460
            listener.onLoadReport(metricReport);
1✔
461
          }
1✔
462
          call.request(1);
1✔
463
        }
1✔
464

465
        void handleStreamClosed(Status status) {
466
          if (Objects.equal(status.getCode(), Code.UNIMPLEMENTED)) {
1✔
467
            disabled = true;
1✔
468
            subchannelLogger.log(
1✔
469
                ChannelLogLevel.ERROR,
470
                "Backend {0} OpenRcaService is disabled. Server returned: {1}",
471
                new Object[] {subchannel.getAllAddresses(), status});
1✔
472
            subchannelLogger.log(ChannelLogLevel.ERROR, "OpenRcaService disabled: {0}", status);
1✔
473
            return;
1✔
474
          }
475
          long delayNanos = 0;
1✔
476
          // Backoff only when no response has been received.
477
          if (!callHasResponded) {
1✔
478
            if (backoffPolicy == null) {
1✔
479
              backoffPolicy = backoffPolicyProvider.get();
1✔
480
            }
481
            delayNanos = backoffPolicy.nextBackoffNanos() - stopwatch.elapsed(TimeUnit.NANOSECONDS);
1✔
482
          }
483
          subchannelLogger.log(
1✔
484
              ChannelLogLevel.DEBUG,
485
              "ORCA reporting stream closed with {0}, backoff in {1} ns",
486
              status,
487
              delayNanos <= 0 ? 0 : delayNanos);
1✔
488
          if (delayNanos <= 0) {
1✔
489
            startRpc();
1✔
490
          } else {
491
            checkState(!isRetryTimerPending(), "Retry double scheduled");
1✔
492
            retryTimer =
1✔
493
                syncContext.schedule(retryTask, delayNanos, TimeUnit.NANOSECONDS, timeService);
1✔
494
          }
495
        }
1✔
496

497
        void cancel(String msg) {
498
          call.cancel(msg, null);
1✔
499
        }
1✔
500

501
        @Override
502
        public String toString() {
503
          return MoreObjects.toStringHelper(this)
×
504
              .add("callStarted", call != null)
×
505
              .add("callHasResponded", callHasResponded)
×
506
              .toString();
×
507
        }
508
      }
509
    }
510
  }
511

512
  @VisibleForTesting
513
  static final class SubchannelImpl extends ForwardingSubchannel {
514
    private final Subchannel delegate;
515
    private final OrcaReportingHelper.OrcaReportingState orcaState;
516
    @Nullable private OrcaOobReportListener reportListener;
517

518
    SubchannelImpl(Subchannel delegate, OrcaReportingHelper.OrcaReportingState orcaState) {
1✔
519
      this.delegate = checkNotNull(delegate, "delegate");
1✔
520
      this.orcaState = checkNotNull(orcaState, "orcaState");
1✔
521
    }
1✔
522

523
    @Override
524
    protected Subchannel delegate() {
525
      return delegate;
1✔
526
    }
527

528
    @Override
529
    public void start(SubchannelStateListener listener) {
530
      if (!orcaState.started) {
1✔
531
        orcaState.init(this, listener);
1✔
532
        super.start(orcaState);
1✔
533
      } else {
534
        super.start(listener);
1✔
535
      }
536
    }
1✔
537

538
    @Override
539
    public Attributes getAttributes() {
540
      return super.getAttributes().toBuilder().set(ORCA_REPORTING_STATE_KEY, this).build();
1✔
541
    }
542
  }
543

544
  /** Configuration for out-of-band ORCA reporting service RPC. */
545
  public static final class OrcaReportingConfig {
546

547
    private final long reportIntervalNanos;
548

549
    private OrcaReportingConfig(long reportIntervalNanos) {
1✔
550
      this.reportIntervalNanos = reportIntervalNanos;
1✔
551
    }
1✔
552

553
    /** Creates a new builder. */
554
    public static Builder newBuilder() {
555
      return new Builder();
1✔
556
    }
557

558
    /** Returns the configured maximum interval of receiving out-of-band ORCA reports. */
559
    public long getReportIntervalNanos() {
560
      return reportIntervalNanos;
1✔
561
    }
562

563
    /** Returns a builder with the same initial values as this object. */
564
    public Builder toBuilder() {
565
      return newBuilder().setReportInterval(reportIntervalNanos, TimeUnit.NANOSECONDS);
1✔
566
    }
567

568
    @Override
569
    public String toString() {
570
      return MoreObjects.toStringHelper(this)
1✔
571
          .add("reportIntervalNanos", reportIntervalNanos)
1✔
572
          .toString();
1✔
573
    }
574

575
    public static final class Builder {
576

577
      private long reportIntervalNanos;
578

579
      Builder() {}
1✔
580

581
      /**
582
       * Sets the maximum expected interval of receiving out-of-band ORCA report. The actual
583
       * reporting interval might be smaller if there are other load balancing policies requesting
584
       * for more frequent cost metric report.
585
       *
586
       * @param reportInterval the maximum expected interval of receiving periodical ORCA reports.
587
       * @param unit time unit of {@code reportInterval} value.
588
       */
589
      public Builder setReportInterval(long reportInterval, TimeUnit unit) {
590
        reportIntervalNanos = unit.toNanos(reportInterval);
1✔
591
        return this;
1✔
592
      }
593

594
      /** Creates a new {@link OrcaReportingConfig} object. */
595
      public OrcaReportingConfig build() {
596
        return new OrcaReportingConfig(reportIntervalNanos);
1✔
597
      }
598
    }
599
  }
600
}
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

© 2025 Coveralls, Inc