snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 0s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 0s
This commit is contained in:
518
internal/xds/balancer/outlierdetection/balancer_ext_test.go
Normal file
518
internal/xds/balancer/outlierdetection/balancer_ext_test.go
Normal file
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2026 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package outlierdetection_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/internal/balancer/stub"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||
"google.golang.org/grpc/internal/xds/balancer/clusterimpl"
|
||||
"google.golang.org/grpc/internal/xds/balancer/outlierdetection"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// TestParseConfig verifies the ParseConfig() method in the Outlier Detection
|
||||
// Balancer.
|
||||
func (s) TestParseConfig(t *testing.T) {
|
||||
const errParseConfigName = "errParseConfigBalancer"
|
||||
stub.Register(errParseConfigName, stub.BalancerFuncs{
|
||||
ParseConfig: func(json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||
return nil, errors.New("some error")
|
||||
},
|
||||
})
|
||||
|
||||
parser := balancer.Get(outlierdetection.Name).(balancer.ConfigParser)
|
||||
const (
|
||||
defaultInterval = iserviceconfig.Duration(10 * time.Second)
|
||||
defaultBaseEjectionTime = iserviceconfig.Duration(30 * time.Second)
|
||||
defaultMaxEjectionTime = iserviceconfig.Duration(300 * time.Second)
|
||||
defaultMaxEjectionPercent = 10
|
||||
defaultSuccessRateStdevFactor = 1900
|
||||
defaultEnforcingSuccessRate = 100
|
||||
defaultSuccessRateMinimumHosts = 5
|
||||
defaultSuccessRateRequestVolume = 100
|
||||
defaultFailurePercentageThreshold = 85
|
||||
defaultEnforcingFailurePercentage = 0
|
||||
defaultFailurePercentageMinimumHosts = 5
|
||||
defaultFailurePercentageRequestVolume = 50
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantCfg serviceconfig.LoadBalancingConfig
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "Default",
|
||||
input: `{
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "TopLevelSet",
|
||||
input: `{
|
||||
"interval": "15s",
|
||||
"maxEjectionTime": "350s",
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
// Should get set fields + defaults for unset fields.
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: iserviceconfig.Duration(15 * time.Second),
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: iserviceconfig.Duration(350 * time.Second),
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SuccessRate_Defaults",
|
||||
input: `{
|
||||
"successRateEjection": {},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
// Should get defaults of success-rate-ejection struct.
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
||||
StdevFactor: defaultSuccessRateStdevFactor,
|
||||
EnforcementPercentage: defaultEnforcingSuccessRate,
|
||||
MinimumHosts: defaultSuccessRateMinimumHosts,
|
||||
RequestVolume: defaultSuccessRateRequestVolume,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SuccessRate_Partial",
|
||||
input: `{
|
||||
"successRateEjection": {
|
||||
"stdevFactor": 1000,
|
||||
"minimumHosts": 5
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
// Should get set fields + defaults for others in success rate
|
||||
// ejection layer.
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
||||
StdevFactor: 1000,
|
||||
EnforcementPercentage: defaultEnforcingSuccessRate,
|
||||
MinimumHosts: 5,
|
||||
RequestVolume: defaultSuccessRateRequestVolume,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SuccessRate_Full",
|
||||
input: `{
|
||||
"successRateEjection": {
|
||||
"stdevFactor": 1000,
|
||||
"enforcementPercentage": 50,
|
||||
"minimumHosts": 5,
|
||||
"requestVolume": 50
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
||||
StdevFactor: 1000,
|
||||
EnforcementPercentage: 50,
|
||||
MinimumHosts: 5,
|
||||
RequestVolume: 50,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FailurePercentage_Defaults",
|
||||
input: `{
|
||||
"failurePercentageEjection": {},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
// Should get defaults of failure percentage ejection layer.
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
||||
Threshold: defaultFailurePercentageThreshold,
|
||||
EnforcementPercentage: defaultEnforcingFailurePercentage,
|
||||
MinimumHosts: defaultFailurePercentageMinimumHosts,
|
||||
RequestVolume: defaultFailurePercentageRequestVolume,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FailurePercentage_Partial",
|
||||
input: `{
|
||||
"failurePercentageEjection": {
|
||||
"threshold": 80,
|
||||
"minimumHosts": 10
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
// Should get set fields + defaults for others in success rate
|
||||
// ejection layer.
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
||||
Threshold: 80,
|
||||
EnforcementPercentage: defaultEnforcingFailurePercentage,
|
||||
MinimumHosts: 10,
|
||||
RequestVolume: defaultFailurePercentageRequestVolume,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FailurePercentage_Full",
|
||||
input: `{
|
||||
"failurePercentageEjection": {
|
||||
"threshold": 80,
|
||||
"enforcementPercentage": 100,
|
||||
"minimumHosts": 10,
|
||||
"requestVolume": 40
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: defaultInterval,
|
||||
BaseEjectionTime: defaultBaseEjectionTime,
|
||||
MaxEjectionTime: defaultMaxEjectionTime,
|
||||
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
||||
Threshold: 80,
|
||||
EnforcementPercentage: 100,
|
||||
MinimumHosts: 10,
|
||||
RequestVolume: 40,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // to make sure zero values aren't overwritten by defaults
|
||||
name: "AllFields_Zero",
|
||||
input: `{
|
||||
"interval": "0s",
|
||||
"baseEjectionTime": "0s",
|
||||
"maxEjectionTime": "0s",
|
||||
"maxEjectionPercent": 0,
|
||||
"successRateEjection": {
|
||||
"stdevFactor": 0,
|
||||
"enforcementPercentage": 0,
|
||||
"minimumHosts": 0,
|
||||
"requestVolume": 0
|
||||
},
|
||||
"failurePercentageEjection": {
|
||||
"threshold": 0,
|
||||
"enforcementPercentage": 0,
|
||||
"minimumHosts": 0,
|
||||
"requestVolume": 0
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{},
|
||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AllFields_Set",
|
||||
input: `{
|
||||
"interval": "10s",
|
||||
"baseEjectionTime": "30s",
|
||||
"maxEjectionTime": "300s",
|
||||
"maxEjectionPercent": 10,
|
||||
"successRateEjection": {
|
||||
"stdevFactor": 1900,
|
||||
"enforcementPercentage": 100,
|
||||
"minimumHosts": 5,
|
||||
"requestVolume": 100
|
||||
},
|
||||
"failurePercentageEjection": {
|
||||
"threshold": 85,
|
||||
"enforcementPercentage": 5,
|
||||
"minimumHosts": 5,
|
||||
"requestVolume": 50
|
||||
},
|
||||
"childPolicy": [
|
||||
{
|
||||
"xds_cluster_impl_experimental": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantCfg: &outlierdetection.LBConfig{
|
||||
Interval: iserviceconfig.Duration(10 * time.Second),
|
||||
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||
MaxEjectionPercent: 10,
|
||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
||||
StdevFactor: 1900,
|
||||
EnforcementPercentage: 100,
|
||||
MinimumHosts: 5,
|
||||
RequestVolume: 100,
|
||||
},
|
||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
||||
Threshold: 85,
|
||||
EnforcementPercentage: 5,
|
||||
MinimumHosts: 5,
|
||||
RequestVolume: 50,
|
||||
},
|
||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||
Name: "xds_cluster_impl_experimental",
|
||||
Config: &clusterimpl.LBConfig{
|
||||
Cluster: "test_cluster",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Interval_Negative",
|
||||
input: `{"interval": "-10s"}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.interval = -10s; must be >= 0",
|
||||
},
|
||||
{
|
||||
name: "BaseEjectionTime_Negative",
|
||||
input: `{"baseEjectionTime": "-10s"}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.base_ejection_time = -10s; must be >= 0",
|
||||
},
|
||||
{
|
||||
name: "MaxEjectionTime_Negative",
|
||||
input: `{"maxEjectionTime": "-10s"}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.max_ejection_time = -10s; must be >= 0",
|
||||
},
|
||||
{
|
||||
name: "MaxEjectionPercent_TooHigh",
|
||||
input: `{"maxEjectionPercent": 150}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.max_ejection_percent = 150; must be <= 100",
|
||||
},
|
||||
{
|
||||
name: "SuccessRate_Enforcement_TooHigh",
|
||||
input: `{
|
||||
"successRateEjection": {
|
||||
"enforcementPercentage": 150
|
||||
}
|
||||
}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.SuccessRateEjection.enforcement_percentage = 150; must be <= 100",
|
||||
},
|
||||
{
|
||||
name: "FailurePercentage_Threshold_TooHigh",
|
||||
input: `{
|
||||
"failurePercentageEjection": {
|
||||
"threshold": 150
|
||||
}
|
||||
}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.threshold = 150; must be <= 100",
|
||||
},
|
||||
{
|
||||
name: "FailurePercentage_Enforcement_TooHigh",
|
||||
input: `{
|
||||
"failurePercentageEjection": {
|
||||
"enforcementPercentage": 150
|
||||
}
|
||||
}`,
|
||||
wantErr: "OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = 150; must be <= 100",
|
||||
},
|
||||
{
|
||||
name: "ChildPolicy_ParseError",
|
||||
input: `{
|
||||
"childPolicy": [
|
||||
{
|
||||
"errParseConfigBalancer": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: "error parsing loadBalancingConfig for policy \"errParseConfigBalancer\"",
|
||||
},
|
||||
{
|
||||
name: "ChildPolicy_NotSupported",
|
||||
input: `{
|
||||
"childPolicy": [
|
||||
{
|
||||
"doesNotExistBalancer": {
|
||||
"cluster": "test_cluster"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: "invalid loadBalancingConfig: no supported policies found",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
gotCfg, gotErr := parser.ParseConfig(json.RawMessage(test.input))
|
||||
if gotErr != nil && !strings.Contains(gotErr.Error(), test.wantErr) {
|
||||
t.Fatalf("ParseConfig(%v) = %v, wantErr %v", test.input, gotErr, test.wantErr)
|
||||
}
|
||||
if (gotErr != nil) != (test.wantErr != "") {
|
||||
t.Fatalf("ParseConfig(%v) = %v, wantErr %v", test.input, gotErr, test.wantErr)
|
||||
}
|
||||
if test.wantErr != "" {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(gotCfg, test.wantCfg); diff != "" {
|
||||
t.Fatalf("parseConfig(%v) got unexpected output, diff (-got +want): %v", string(test.input), diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user