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

grpc / grpc-java / #20034

29 Oct 2025 05:05PM UTC coverage: 88.514% (-0.02%) from 88.533%
#20034

push

github

ejona86
Include causal status details in higher-level statuses

When an operation fails and we want to produce a new status at a higher
level, we commonly are turning the first status into an exception to
attach to the new exception. We should instead prefer to keep as much
information in the status description itself, as cause is not as
reliable to be logged/propagated.

I do expect long-term we'll want to expose an API in grpc-api for this,
but for the moment let's keep it internal. In particular, we'd have to
figure out its name. I could also believe we might want different
formatting, which becomes a clearer discussion when we can see the
usages.

I'm pretty certain there are some other places that could benefit from
this utility, as I remember really wishing I had these functions a month
or two ago. But these are the places I could immediately find.

OutlierDetectionLoadBalancerConfig had its status code changed from
INTERNAL to UNAVAILABLE because the value comes externally, and so isn't
a gRPC bug or such. I didn't change the xds policies in the same way
because it's murkier as the configuration for those is largely generated
within xds itself.

34957 of 39493 relevant lines covered (88.51%)

0.89 hits per line

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

90.32
/../util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
1
/*
2
 * Copyright 2022 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 com.google.common.base.Ticker;
20
import io.grpc.Internal;
21
import io.grpc.LoadBalancer;
22
import io.grpc.LoadBalancer.Helper;
23
import io.grpc.LoadBalancerProvider;
24
import io.grpc.NameResolver.ConfigOrError;
25
import io.grpc.Status;
26
import io.grpc.internal.GrpcUtil;
27
import io.grpc.internal.JsonUtil;
28
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
29
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.FailurePercentageEjection;
30
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.SuccessRateEjection;
31
import java.util.Map;
32

33
@Internal
34
public final class OutlierDetectionLoadBalancerProvider extends LoadBalancerProvider {
1✔
35

36
  @Override
37
  public LoadBalancer newLoadBalancer(Helper helper) {
38
    return new OutlierDetectionLoadBalancer(helper, Ticker.systemTicker());
1✔
39
  }
40

41
  @Override
42
  public boolean isAvailable() {
43
    return true;
1✔
44
  }
45

46
  @Override
47
  public int getPriority() {
48
    return 5;
1✔
49
  }
50

51
  @Override
52
  public String getPolicyName() {
53
    return "outlier_detection_experimental";
1✔
54
  }
55

56
  @Override
57
  public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) {
58
    try {
59
      return parseLoadBalancingPolicyConfigInternal(rawConfig);
1✔
60
    } catch (RuntimeException e) {
×
61
      return ConfigOrError.fromError(
×
62
          Status.UNAVAILABLE.withCause(e).withDescription(
×
63
              "Failed parsing configuration for " + getPolicyName()));
×
64
    }
65
  }
66

67
  private ConfigOrError parseLoadBalancingPolicyConfigInternal(Map<String, ?> rawConfig) {
68
    // Common configuration.
69
    Long intervalNanos = JsonUtil.getStringAsDuration(rawConfig, "interval");
1✔
70
    Long baseEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "baseEjectionTime");
1✔
71
    Long maxEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "maxEjectionTime");
1✔
72
    Integer maxEjectionPercentage = JsonUtil.getNumberAsInteger(rawConfig,
1✔
73
        "maxEjectionPercentage");
74

75
    OutlierDetectionLoadBalancerConfig.Builder configBuilder
1✔
76
        = new OutlierDetectionLoadBalancerConfig.Builder();
77
    if (intervalNanos != null) {
1✔
78
      configBuilder.setIntervalNanos(intervalNanos);
1✔
79
    }
80
    if (baseEjectionTimeNanos != null) {
1✔
81
      configBuilder.setBaseEjectionTimeNanos(baseEjectionTimeNanos);
1✔
82
    }
83
    if (maxEjectionTimeNanos != null) {
1✔
84
      configBuilder.setMaxEjectionTimeNanos(maxEjectionTimeNanos);
1✔
85
    }
86
    if (maxEjectionPercentage != null) {
1✔
87
      configBuilder.setMaxEjectionPercent(maxEjectionPercentage);
1✔
88
    }
89

90
    // Success rate ejection specific configuration.
91
    Map<String, ?> rawSuccessRateEjection = JsonUtil.getObject(rawConfig, "successRateEjection");
1✔
92
    if (rawSuccessRateEjection != null) {
1✔
93
      SuccessRateEjection.Builder successRateEjectionBuilder = new SuccessRateEjection.Builder();
1✔
94

95
      Integer stdevFactor = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "stdevFactor");
1✔
96
      Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawSuccessRateEjection,
1✔
97
          "enforcementPercentage");
98
      Integer minimumHosts = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "minimumHosts");
1✔
99
      Integer requestVolume = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "requestVolume");
1✔
100

101
      if (stdevFactor != null) {
1✔
102
        successRateEjectionBuilder.setStdevFactor(stdevFactor);
1✔
103
      }
104
      if (enforcementPercentage != null) {
1✔
105
        successRateEjectionBuilder.setEnforcementPercentage(enforcementPercentage);
1✔
106
      }
107
      if (minimumHosts != null) {
1✔
108
        successRateEjectionBuilder.setMinimumHosts(minimumHosts);
1✔
109
      }
110
      if (requestVolume != null) {
1✔
111
        successRateEjectionBuilder.setRequestVolume(requestVolume);
1✔
112
      }
113

114
      configBuilder.setSuccessRateEjection(successRateEjectionBuilder.build());
1✔
115
    }
116

117
    // Failure percentage ejection specific configuration.
118
    Map<String, ?> rawFailurePercentageEjection = JsonUtil.getObject(rawConfig,
1✔
119
        "failurePercentageEjection");
120
    if (rawFailurePercentageEjection != null) {
1✔
121
      FailurePercentageEjection.Builder failurePercentageEjectionBuilder
1✔
122
          = new FailurePercentageEjection.Builder();
123

124
      Integer threshold = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, "threshold");
1✔
125
      Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
1✔
126
          "enforcementPercentage");
127
      Integer minimumHosts = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
1✔
128
          "minimumHosts");
129
      Integer requestVolume = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
1✔
130
          "requestVolume");
131

132
      if (threshold != null) {
1✔
133
        failurePercentageEjectionBuilder.setThreshold(threshold);
1✔
134
      }
135
      if (enforcementPercentage != null) {
1✔
136
        failurePercentageEjectionBuilder.setEnforcementPercentage(enforcementPercentage);
1✔
137
      }
138
      if (minimumHosts != null) {
1✔
139
        failurePercentageEjectionBuilder.setMinimumHosts(minimumHosts);
1✔
140
      }
141
      if (requestVolume != null) {
1✔
142
        failurePercentageEjectionBuilder.setRequestVolume(requestVolume);
1✔
143
      }
144

145
      configBuilder.setFailurePercentageEjection(failurePercentageEjectionBuilder.build());
1✔
146
    }
147

148
    // Child load balancer configuration.
149
    ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig(
1✔
150
        JsonUtil.getListOfObjects(rawConfig, "childPolicy"));
1✔
151
    if (childConfig.getError() != null) {
1✔
152
      return ConfigOrError.fromError(GrpcUtil.statusWithDetails(
×
153
          Status.Code.UNAVAILABLE,
154
          "Failed to parse child in outlier_detection_experimental",
155
          childConfig.getError()));
×
156
    }
157
    configBuilder.setChildConfig(childConfig.getConfig());
1✔
158

159
    return ConfigOrError.fromConfig(configBuilder.build());
1✔
160
  }
161
}
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