mirror of https://github.com/grpc/grpc-go.git
Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
|
5b67e5ea44 | |
|
d0f5150384 | |
|
997c1ea101 | |
|
2b6ff72f08 | |
|
799642536e | |
|
a5ae5c6408 |
|
@ -419,7 +419,11 @@ func (w *weightedSubConn) OnLoadReport(load *v3orcapb.OrcaLoadReport) {
|
||||||
w.logger.Infof("Received load report for subchannel %v: %v", w.SubConn, load)
|
w.logger.Infof("Received load report for subchannel %v: %v", w.SubConn, load)
|
||||||
}
|
}
|
||||||
// Update weights of this subchannel according to the reported load
|
// Update weights of this subchannel according to the reported load
|
||||||
if load.CpuUtilization == 0 || load.RpsFractional == 0 {
|
utilization := load.ApplicationUtilization
|
||||||
|
if utilization == 0 {
|
||||||
|
utilization = load.CpuUtilization
|
||||||
|
}
|
||||||
|
if utilization == 0 || load.RpsFractional == 0 {
|
||||||
if w.logger.V(2) {
|
if w.logger.V(2) {
|
||||||
w.logger.Infof("Ignoring empty load report for subchannel %v", w.SubConn)
|
w.logger.Infof("Ignoring empty load report for subchannel %v", w.SubConn)
|
||||||
}
|
}
|
||||||
|
@ -430,7 +434,7 @@ func (w *weightedSubConn) OnLoadReport(load *v3orcapb.OrcaLoadReport) {
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
errorRate := load.Eps / load.RpsFractional
|
errorRate := load.Eps / load.RpsFractional
|
||||||
w.weightVal = load.RpsFractional / (load.CpuUtilization + errorRate*w.cfg.ErrorUtilizationPenalty)
|
w.weightVal = load.RpsFractional / (utilization + errorRate*w.cfg.ErrorUtilizationPenalty)
|
||||||
if w.logger.V(2) {
|
if w.logger.V(2) {
|
||||||
w.logger.Infof("New weight for subchannel %v: %v", w.SubConn, w.weightVal)
|
w.logger.Infof("New weight for subchannel %v: %v", w.SubConn, w.weightVal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ func startServer(t *testing.T, r reportType) *testServer {
|
||||||
if r := orca.CallMetricsRecorderFromContext(ctx); r != nil {
|
if r := orca.CallMetricsRecorderFromContext(ctx); r != nil {
|
||||||
// Copy metrics from what the test set in cmr into r.
|
// Copy metrics from what the test set in cmr into r.
|
||||||
sm := cmr.(orca.ServerMetricsProvider).ServerMetrics()
|
sm := cmr.(orca.ServerMetricsProvider).ServerMetrics()
|
||||||
r.SetCPUUtilization(sm.CPUUtilization)
|
r.SetApplicationUtilization(sm.AppUtilization)
|
||||||
r.SetQPS(sm.QPS)
|
r.SetQPS(sm.QPS)
|
||||||
r.SetEPS(sm.EPS)
|
r.SetEPS(sm.EPS)
|
||||||
}
|
}
|
||||||
|
@ -230,10 +230,10 @@ func (s) TestBalancer_TwoAddresses_ReportingEnabledPerCall(t *testing.T) {
|
||||||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
||||||
// disproportionately to srv2 (10:1).
|
// disproportionately to srv2 (10:1).
|
||||||
srv1.callMetrics.SetQPS(10.0)
|
srv1.callMetrics.SetQPS(10.0)
|
||||||
srv1.callMetrics.SetCPUUtilization(1.0)
|
srv1.callMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
srv2.callMetrics.SetQPS(10.0)
|
srv2.callMetrics.SetQPS(10.0)
|
||||||
srv2.callMetrics.SetCPUUtilization(.1)
|
srv2.callMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
sc := svcConfig(t, perCallConfig)
|
sc := svcConfig(t, perCallConfig)
|
||||||
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
||||||
|
@ -253,33 +253,58 @@ func (s) TestBalancer_TwoAddresses_ReportingEnabledPerCall(t *testing.T) {
|
||||||
// Tests two addresses with OOB ORCA reporting enabled. Checks the backends
|
// Tests two addresses with OOB ORCA reporting enabled. Checks the backends
|
||||||
// are called in the appropriate ratios.
|
// are called in the appropriate ratios.
|
||||||
func (s) TestBalancer_TwoAddresses_ReportingEnabledOOB(t *testing.T) {
|
func (s) TestBalancer_TwoAddresses_ReportingEnabledOOB(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
testCases := []struct {
|
||||||
defer cancel()
|
name string
|
||||||
|
utilSetter func(orca.ServerMetricsRecorder, float64)
|
||||||
|
}{{
|
||||||
|
name: "application_utilization",
|
||||||
|
utilSetter: func(smr orca.ServerMetricsRecorder, val float64) {
|
||||||
|
smr.SetApplicationUtilization(val)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "cpu_utilization",
|
||||||
|
utilSetter: func(smr orca.ServerMetricsRecorder, val float64) {
|
||||||
|
smr.SetCPUUtilization(val)
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "application over cpu",
|
||||||
|
utilSetter: func(smr orca.ServerMetricsRecorder, val float64) {
|
||||||
|
smr.SetApplicationUtilization(val)
|
||||||
|
smr.SetCPUUtilization(2.0) // ignored because ApplicationUtilization is set
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
srv1 := startServer(t, reportOOB)
|
for _, tc := range testCases {
|
||||||
srv2 := startServer(t, reportOOB)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
srv1 := startServer(t, reportOOB)
|
||||||
// disproportionately to srv2 (10:1).
|
srv2 := startServer(t, reportOOB)
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
// disproportionately to srv2 (10:1).
|
||||||
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
|
tc.utilSetter(srv1.oobMetrics, 1.0)
|
||||||
|
|
||||||
sc := svcConfig(t, oobConfig)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
tc.utilSetter(srv2.oobMetrics, 0.1)
|
||||||
t.Fatalf("Error starting client: %v", err)
|
|
||||||
|
sc := svcConfig(t, oobConfig)
|
||||||
|
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
||||||
|
t.Fatalf("Error starting client: %v", err)
|
||||||
|
}
|
||||||
|
addrs := []resolver.Address{{Addr: srv1.Address}, {Addr: srv2.Address}}
|
||||||
|
srv1.R.UpdateState(resolver.State{Addresses: addrs})
|
||||||
|
|
||||||
|
// Call each backend once to ensure the weights have been received.
|
||||||
|
ensureReached(ctx, t, srv1.Client, 2)
|
||||||
|
|
||||||
|
// Wait for the weight update period to allow the new weights to be processed.
|
||||||
|
time.Sleep(weightUpdatePeriod)
|
||||||
|
checkWeights(ctx, t, srvWeight{srv1, 1}, srvWeight{srv2, 10})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
addrs := []resolver.Address{{Addr: srv1.Address}, {Addr: srv2.Address}}
|
|
||||||
srv1.R.UpdateState(resolver.State{Addresses: addrs})
|
|
||||||
|
|
||||||
// Call each backend once to ensure the weights have been received.
|
|
||||||
ensureReached(ctx, t, srv1.Client, 2)
|
|
||||||
|
|
||||||
// Wait for the weight update period to allow the new weights to be processed.
|
|
||||||
time.Sleep(weightUpdatePeriod)
|
|
||||||
checkWeights(ctx, t, srvWeight{srv1, 1}, srvWeight{srv2, 10})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests two addresses with OOB ORCA reporting enabled, where the reports
|
// Tests two addresses with OOB ORCA reporting enabled, where the reports
|
||||||
|
@ -295,10 +320,10 @@ func (s) TestBalancer_TwoAddresses_UpdateLoads(t *testing.T) {
|
||||||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
||||||
// disproportionately to srv2 (10:1).
|
// disproportionately to srv2 (10:1).
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
sc := svcConfig(t, oobConfig)
|
sc := svcConfig(t, oobConfig)
|
||||||
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
||||||
|
@ -317,10 +342,10 @@ func (s) TestBalancer_TwoAddresses_UpdateLoads(t *testing.T) {
|
||||||
// Update the loads so srv2 is loaded and srv1 is not; ensure RPCs are
|
// Update the loads so srv2 is loaded and srv1 is not; ensure RPCs are
|
||||||
// routed disproportionately to srv1.
|
// routed disproportionately to srv1.
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(.1)
|
srv1.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(1.0)
|
srv2.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
// Wait for the weight update period to allow the new weights to be processed.
|
// Wait for the weight update period to allow the new weights to be processed.
|
||||||
time.Sleep(weightUpdatePeriod + oobReportingInterval)
|
time.Sleep(weightUpdatePeriod + oobReportingInterval)
|
||||||
|
@ -340,19 +365,19 @@ func (s) TestBalancer_TwoAddresses_OOBThenPerCall(t *testing.T) {
|
||||||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
||||||
// disproportionately to srv2 (10:1).
|
// disproportionately to srv2 (10:1).
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
// For per-call metrics (not used initially), srv2 reports that it is
|
// For per-call metrics (not used initially), srv2 reports that it is
|
||||||
// loaded and srv1 reports low load. After confirming OOB works, switch to
|
// loaded and srv1 reports low load. After confirming OOB works, switch to
|
||||||
// per-call and confirm the new routing weights are applied.
|
// per-call and confirm the new routing weights are applied.
|
||||||
srv1.callMetrics.SetQPS(10.0)
|
srv1.callMetrics.SetQPS(10.0)
|
||||||
srv1.callMetrics.SetCPUUtilization(.1)
|
srv1.callMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
srv2.callMetrics.SetQPS(10.0)
|
srv2.callMetrics.SetQPS(10.0)
|
||||||
srv2.callMetrics.SetCPUUtilization(1.0)
|
srv2.callMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
sc := svcConfig(t, oobConfig)
|
sc := svcConfig(t, oobConfig)
|
||||||
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
||||||
|
@ -396,13 +421,13 @@ func (s) TestBalancer_TwoAddresses_ErrorPenalty(t *testing.T) {
|
||||||
// to 0.9 which will cause the weights to be equal and RPCs to be routed
|
// to 0.9 which will cause the weights to be equal and RPCs to be routed
|
||||||
// 50/50.
|
// 50/50.
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
srv1.oobMetrics.SetEPS(0)
|
srv1.oobMetrics.SetEPS(0)
|
||||||
// srv1 weight before: 10.0 / 1.0 = 10.0
|
// srv1 weight before: 10.0 / 1.0 = 10.0
|
||||||
// srv1 weight after: 10.0 / 1.0 = 10.0
|
// srv1 weight after: 10.0 / 1.0 = 10.0
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
srv2.oobMetrics.SetEPS(10.0)
|
srv2.oobMetrics.SetEPS(10.0)
|
||||||
// srv2 weight before: 10.0 / 0.1 = 100.0
|
// srv2 weight before: 10.0 / 0.1 = 100.0
|
||||||
// srv2 weight after: 10.0 / 1.0 = 10.0
|
// srv2 weight after: 10.0 / 1.0 = 10.0
|
||||||
|
@ -476,10 +501,10 @@ func (s) TestBalancer_TwoAddresses_BlackoutPeriod(t *testing.T) {
|
||||||
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
// srv1 starts loaded and srv2 starts without load; ensure RPCs are routed
|
||||||
// disproportionately to srv2 (10:1).
|
// disproportionately to srv2 (10:1).
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
cfg := oobConfig
|
cfg := oobConfig
|
||||||
cfg.BlackoutPeriod = tc.blackoutPeriodCfg
|
cfg.BlackoutPeriod = tc.blackoutPeriodCfg
|
||||||
|
@ -544,10 +569,10 @@ func (s) TestBalancer_TwoAddresses_WeightExpiration(t *testing.T) {
|
||||||
// is 1 minute but the weights expire in 1 second, routing will go to 50/50
|
// is 1 minute but the weights expire in 1 second, routing will go to 50/50
|
||||||
// after the weights expire.
|
// after the weights expire.
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
|
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
cfg := oobConfig
|
cfg := oobConfig
|
||||||
cfg.OOBReportingPeriod = stringp("60s")
|
cfg.OOBReportingPeriod = stringp("60s")
|
||||||
|
@ -594,16 +619,16 @@ func (s) TestBalancer_AddressesChanging(t *testing.T) {
|
||||||
|
|
||||||
// srv1: weight 10
|
// srv1: weight 10
|
||||||
srv1.oobMetrics.SetQPS(10.0)
|
srv1.oobMetrics.SetQPS(10.0)
|
||||||
srv1.oobMetrics.SetCPUUtilization(1.0)
|
srv1.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
// srv2: weight 100
|
// srv2: weight 100
|
||||||
srv2.oobMetrics.SetQPS(10.0)
|
srv2.oobMetrics.SetQPS(10.0)
|
||||||
srv2.oobMetrics.SetCPUUtilization(.1)
|
srv2.oobMetrics.SetApplicationUtilization(.1)
|
||||||
// srv3: weight 20
|
// srv3: weight 20
|
||||||
srv3.oobMetrics.SetQPS(20.0)
|
srv3.oobMetrics.SetQPS(20.0)
|
||||||
srv3.oobMetrics.SetCPUUtilization(1.0)
|
srv3.oobMetrics.SetApplicationUtilization(1.0)
|
||||||
// srv4: weight 200
|
// srv4: weight 200
|
||||||
srv4.oobMetrics.SetQPS(20.0)
|
srv4.oobMetrics.SetQPS(20.0)
|
||||||
srv4.oobMetrics.SetCPUUtilization(.1)
|
srv4.oobMetrics.SetApplicationUtilization(.1)
|
||||||
|
|
||||||
sc := svcConfig(t, oobConfig)
|
sc := svcConfig(t, oobConfig)
|
||||||
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
if err := srv1.StartClient(grpc.WithDefaultServiceConfig(sc)); err != nil {
|
||||||
|
|
|
@ -1033,8 +1033,10 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) {
|
||||||
|
|
||||||
// We have to defer here because GracefulClose => Close => onClose, which
|
// We have to defer here because GracefulClose => Close => onClose, which
|
||||||
// requires locking ac.mu.
|
// requires locking ac.mu.
|
||||||
defer ac.transport.GracefulClose()
|
if ac.transport != nil {
|
||||||
ac.transport = nil
|
defer ac.transport.GracefulClose()
|
||||||
|
ac.transport = nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
ac.updateConnectivityState(connectivity.Idle, nil)
|
ac.updateConnectivityState(connectivity.Idle, nil)
|
||||||
|
|
|
@ -3,7 +3,7 @@ module google.golang.org/grpc/examples
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
golang.org/x/oauth2 v0.7.0
|
golang.org/x/oauth2 v0.7.0
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
|
||||||
|
|
|
@ -627,8 +627,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLWvQsR9CzAKt2e+EWV2yX9oXQ4=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
|
@ -638,7 +638,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -5,7 +5,7 @@ go 1.17
|
||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.2.0
|
github.com/cespare/xxhash/v2 v2.2.0
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe
|
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f
|
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f
|
||||||
github.com/golang/glog v1.1.0
|
github.com/golang/glog v1.1.0
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.3
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -13,8 +13,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLWvQsR9CzAKt2e+EWV2yX9oXQ4=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA=
|
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA=
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2023 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 nop implements a balancer with all of its balancer operations as
|
||||||
|
// no-ops, other than returning a Transient Failure Picker on a Client Conn
|
||||||
|
// update.
|
||||||
|
package nop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/base"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bal is a balancer with all of its balancer operations as no-ops, other than
|
||||||
|
// returning a Transient Failure Picker on a Client Conn update.
|
||||||
|
type bal struct {
|
||||||
|
cc balancer.ClientConn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBalancer returns a no-op balancer.
|
||||||
|
func NewBalancer(cc balancer.ClientConn, err error) balancer.Balancer {
|
||||||
|
return &bal{
|
||||||
|
cc: cc,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClientConnState updates the bal's Client Conn with an Error Picker
|
||||||
|
// and a Connectivity State of TRANSIENT_FAILURE.
|
||||||
|
func (b *bal) UpdateClientConnState(_ balancer.ClientConnState) error {
|
||||||
|
b.cc.UpdateState(balancer.State{
|
||||||
|
Picker: base.NewErrPicker(b.err),
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolverError is a no-op.
|
||||||
|
func (b *bal) ResolverError(_ error) {}
|
||||||
|
|
||||||
|
// UpdateSubConnState is a no-op.
|
||||||
|
func (b *bal) UpdateSubConnState(_ balancer.SubConn, _ balancer.SubConnState) {}
|
||||||
|
|
||||||
|
// Close is a no-op.
|
||||||
|
func (b *bal) Close() {}
|
|
@ -18,7 +18,7 @@ require (
|
||||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.12 // indirect
|
contrib.go.opencensus.io/exporter/stackdriver v0.13.12 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.44.162 // indirect
|
github.com/aws/aws-sdk-go v1.44.162 // indirect
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 // indirect
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect
|
github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
|
|
@ -638,8 +638,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLWvQsR9CzAKt2e+EWV2yX9oXQ4=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
|
@ -23,13 +23,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/grpc/internal/grpctest"
|
|
||||||
"google.golang.org/grpc/internal/pretty"
|
"google.golang.org/grpc/internal/pretty"
|
||||||
"google.golang.org/grpc/internal/stubserver"
|
"google.golang.org/grpc/internal/stubserver"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
|
@ -41,16 +39,6 @@ import (
|
||||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type s struct {
|
|
||||||
grpctest.Tester
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
grpctest.RunSubTests(t, s{})
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultTestTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// TestE2ECallMetricsUnary tests the injection of custom backend metrics from
|
// TestE2ECallMetricsUnary tests the injection of custom backend metrics from
|
||||||
// the server application for a unary RPC, and verifies that expected load
|
// the server application for a unary RPC, and verifies that expected load
|
||||||
// reports are received at the client.
|
// reports are received at the client.
|
||||||
|
@ -65,9 +53,9 @@ func (s) TestE2ECallMetricsUnary(t *testing.T) {
|
||||||
injectMetrics: true,
|
injectMetrics: true,
|
||||||
wantProto: &v3orcapb.OrcaLoadReport{
|
wantProto: &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 1.0,
|
CpuUtilization: 1.0,
|
||||||
MemUtilization: 50.0,
|
MemUtilization: 0.9,
|
||||||
RequestCost: map[string]float64{"queryCost": 25.0},
|
RequestCost: map[string]float64{"queryCost": 25.0},
|
||||||
Utilization: map[string]float64{"queueSize": 75.0},
|
Utilization: map[string]float64{"queueSize": 0.75},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -92,7 +80,7 @@ func (s) TestE2ECallMetricsUnary(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
recorder.SetMemoryUtilization(50.0)
|
recorder.SetMemoryUtilization(0.9)
|
||||||
// This value will be overwritten by a write to the same metric
|
// This value will be overwritten by a write to the same metric
|
||||||
// from the server handler.
|
// from the server handler.
|
||||||
recorder.SetNamedUtilization("queueSize", 1.0)
|
recorder.SetNamedUtilization("queueSize", 1.0)
|
||||||
|
@ -114,7 +102,7 @@ func (s) TestE2ECallMetricsUnary(t *testing.T) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
recorder.SetRequestCost("queryCost", 25.0)
|
recorder.SetRequestCost("queryCost", 25.0)
|
||||||
recorder.SetNamedUtilization("queueSize", 75.0)
|
recorder.SetNamedUtilization("queueSize", 0.75)
|
||||||
return &testpb.Empty{}, nil
|
return &testpb.Empty{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -171,9 +159,9 @@ func (s) TestE2ECallMetricsStreaming(t *testing.T) {
|
||||||
injectMetrics: true,
|
injectMetrics: true,
|
||||||
wantProto: &v3orcapb.OrcaLoadReport{
|
wantProto: &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 1.0,
|
CpuUtilization: 1.0,
|
||||||
MemUtilization: 50.0,
|
MemUtilization: 0.5,
|
||||||
RequestCost: map[string]float64{"queryCost": 25.0},
|
RequestCost: map[string]float64{"queryCost": 0.25},
|
||||||
Utilization: map[string]float64{"queueSize": 75.0},
|
Utilization: map[string]float64{"queueSize": 0.75},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -198,7 +186,7 @@ func (s) TestE2ECallMetricsStreaming(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
recorder.SetMemoryUtilization(50.0)
|
recorder.SetMemoryUtilization(0.5)
|
||||||
// This value will be overwritten by a write to the same metric
|
// This value will be overwritten by a write to the same metric
|
||||||
// from the server handler.
|
// from the server handler.
|
||||||
recorder.SetNamedUtilization("queueSize", 1.0)
|
recorder.SetNamedUtilization("queueSize", 1.0)
|
||||||
|
@ -217,8 +205,8 @@ func (s) TestE2ECallMetricsStreaming(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
recorder.SetRequestCost("queryCost", 25.0)
|
recorder.SetRequestCost("queryCost", 0.25)
|
||||||
recorder.SetNamedUtilization("queueSize", 75.0)
|
recorder.SetNamedUtilization("queueSize", 0.75)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streaming implementation replies with a dummy response until the
|
// Streaming implementation replies with a dummy response until the
|
||||||
|
|
|
@ -20,9 +20,11 @@ package orca_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
"google.golang.org/grpc/internal/pretty"
|
"google.golang.org/grpc/internal/pretty"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/orca/internal"
|
"google.golang.org/grpc/orca/internal"
|
||||||
|
@ -30,7 +32,17 @@ import (
|
||||||
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
|
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToLoadReport(t *testing.T) {
|
type s struct {
|
||||||
|
grpctest.Tester
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
grpctest.RunSubTests(t, s{})
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTestTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
func (s) TestToLoadReport(t *testing.T) {
|
||||||
goodReport := &v3orcapb.OrcaLoadReport{
|
goodReport := &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 1.0,
|
CpuUtilization: 1.0,
|
||||||
MemUtilization: 50.0,
|
MemUtilization: 50.0,
|
||||||
|
|
|
@ -158,12 +158,12 @@ func (s) TestProducer(t *testing.T) {
|
||||||
|
|
||||||
// Set a few metrics and wait for them on the client side.
|
// Set a few metrics and wait for them on the client side.
|
||||||
smr.SetCPUUtilization(10)
|
smr.SetCPUUtilization(10)
|
||||||
smr.SetMemoryUtilization(100)
|
smr.SetMemoryUtilization(0.1)
|
||||||
smr.SetNamedUtilization("bob", 555)
|
smr.SetNamedUtilization("bob", 0.555)
|
||||||
loadReportWant := &v3orcapb.OrcaLoadReport{
|
loadReportWant := &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 10,
|
CpuUtilization: 10,
|
||||||
MemUtilization: 100,
|
MemUtilization: 0.1,
|
||||||
Utilization: map[string]float64{"bob": 555},
|
Utilization: map[string]float64{"bob": 0.555},
|
||||||
}
|
}
|
||||||
|
|
||||||
testReport:
|
testReport:
|
||||||
|
@ -181,13 +181,13 @@ testReport:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change and add metrics and wait for them on the client side.
|
// Change and add metrics and wait for them on the client side.
|
||||||
smr.SetCPUUtilization(50)
|
smr.SetCPUUtilization(0.5)
|
||||||
smr.SetMemoryUtilization(200)
|
smr.SetMemoryUtilization(0.2)
|
||||||
smr.SetNamedUtilization("mary", 321)
|
smr.SetNamedUtilization("mary", 0.321)
|
||||||
loadReportWant = &v3orcapb.OrcaLoadReport{
|
loadReportWant = &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 50,
|
CpuUtilization: 0.5,
|
||||||
MemUtilization: 200,
|
MemUtilization: 0.2,
|
||||||
Utilization: map[string]float64{"bob": 555, "mary": 321},
|
Utilization: map[string]float64{"bob": 0.555, "mary": 0.321},
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -322,8 +322,8 @@ func (s) TestProducerBackoff(t *testing.T) {
|
||||||
// Define a load report to send and expect the client to see.
|
// Define a load report to send and expect the client to see.
|
||||||
loadReportWant := &v3orcapb.OrcaLoadReport{
|
loadReportWant := &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 10,
|
CpuUtilization: 10,
|
||||||
MemUtilization: 100,
|
MemUtilization: 0.1,
|
||||||
Utilization: map[string]float64{"bob": 555},
|
Utilization: map[string]float64{"bob": 0.555},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unblock the fake.
|
// Unblock the fake.
|
||||||
|
@ -444,8 +444,8 @@ func (s) TestProducerMultipleListeners(t *testing.T) {
|
||||||
// Define a load report to send and expect the client to see.
|
// Define a load report to send and expect the client to see.
|
||||||
loadReportWant := &v3orcapb.OrcaLoadReport{
|
loadReportWant := &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 10,
|
CpuUtilization: 10,
|
||||||
MemUtilization: 100,
|
MemUtilization: 0.1,
|
||||||
Utilization: map[string]float64{"bob": 555},
|
Utilization: map[string]float64{"bob": 0.555},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive reports and update counts for the three listeners.
|
// Receive reports and update counts for the three listeners.
|
||||||
|
|
|
@ -27,8 +27,9 @@ import (
|
||||||
// ServerMetrics is the data returned from a server to a client to describe the
|
// ServerMetrics is the data returned from a server to a client to describe the
|
||||||
// current state of the server and/or the cost of a request when used per-call.
|
// current state of the server and/or the cost of a request when used per-call.
|
||||||
type ServerMetrics struct {
|
type ServerMetrics struct {
|
||||||
CPUUtilization float64 // CPU utilization: [0, 1.0]; unset=-1
|
CPUUtilization float64 // CPU utilization: [0, inf); unset=-1
|
||||||
MemUtilization float64 // Memory utilization: [0, 1.0]; unset=-1
|
MemUtilization float64 // Memory utilization: [0, 1.0]; unset=-1
|
||||||
|
AppUtilization float64 // Application utilization: [0, inf); unset=-1
|
||||||
QPS float64 // queries per second: [0, inf); unset=-1
|
QPS float64 // queries per second: [0, inf); unset=-1
|
||||||
EPS float64 // errors per second: [0, inf); unset=-1
|
EPS float64 // errors per second: [0, inf); unset=-1
|
||||||
|
|
||||||
|
@ -52,6 +53,9 @@ func (sm *ServerMetrics) toLoadReportProto() *v3orcapb.OrcaLoadReport {
|
||||||
if sm.MemUtilization != -1 {
|
if sm.MemUtilization != -1 {
|
||||||
ret.MemUtilization = sm.MemUtilization
|
ret.MemUtilization = sm.MemUtilization
|
||||||
}
|
}
|
||||||
|
if sm.AppUtilization != -1 {
|
||||||
|
ret.ApplicationUtilization = sm.AppUtilization
|
||||||
|
}
|
||||||
if sm.QPS != -1 {
|
if sm.QPS != -1 {
|
||||||
ret.RpsFractional = sm.QPS
|
ret.RpsFractional = sm.QPS
|
||||||
}
|
}
|
||||||
|
@ -63,21 +67,24 @@ func (sm *ServerMetrics) toLoadReportProto() *v3orcapb.OrcaLoadReport {
|
||||||
|
|
||||||
// merge merges o into sm, overwriting any values present in both.
|
// merge merges o into sm, overwriting any values present in both.
|
||||||
func (sm *ServerMetrics) merge(o *ServerMetrics) {
|
func (sm *ServerMetrics) merge(o *ServerMetrics) {
|
||||||
|
mergeMap(sm.Utilization, o.Utilization)
|
||||||
|
mergeMap(sm.RequestCost, o.RequestCost)
|
||||||
|
mergeMap(sm.NamedMetrics, o.NamedMetrics)
|
||||||
if o.CPUUtilization != -1 {
|
if o.CPUUtilization != -1 {
|
||||||
sm.CPUUtilization = o.CPUUtilization
|
sm.CPUUtilization = o.CPUUtilization
|
||||||
}
|
}
|
||||||
if o.MemUtilization != -1 {
|
if o.MemUtilization != -1 {
|
||||||
sm.MemUtilization = o.MemUtilization
|
sm.MemUtilization = o.MemUtilization
|
||||||
}
|
}
|
||||||
|
if o.AppUtilization != -1 {
|
||||||
|
sm.AppUtilization = o.AppUtilization
|
||||||
|
}
|
||||||
if o.QPS != -1 {
|
if o.QPS != -1 {
|
||||||
sm.QPS = o.QPS
|
sm.QPS = o.QPS
|
||||||
}
|
}
|
||||||
if o.EPS != -1 {
|
if o.EPS != -1 {
|
||||||
sm.EPS = o.EPS
|
sm.EPS = o.EPS
|
||||||
}
|
}
|
||||||
mergeMap(sm.Utilization, o.Utilization)
|
|
||||||
mergeMap(sm.RequestCost, o.RequestCost)
|
|
||||||
mergeMap(sm.NamedMetrics, o.NamedMetrics)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeMap(a, b map[string]float64) {
|
func mergeMap(a, b map[string]float64) {
|
||||||
|
@ -91,34 +98,46 @@ func mergeMap(a, b map[string]float64) {
|
||||||
type ServerMetricsRecorder interface {
|
type ServerMetricsRecorder interface {
|
||||||
ServerMetricsProvider
|
ServerMetricsProvider
|
||||||
|
|
||||||
// SetCPUUtilization sets the relevant server metric.
|
// SetCPUUtilization sets the CPU utilization server metric. Must be
|
||||||
|
// greater than zero.
|
||||||
SetCPUUtilization(float64)
|
SetCPUUtilization(float64)
|
||||||
// DeleteCPUUtilization deletes the relevant server metric to prevent it
|
// DeleteCPUUtilization deletes the CPU utilization server metric to
|
||||||
// from being sent.
|
// prevent it from being sent.
|
||||||
DeleteCPUUtilization()
|
DeleteCPUUtilization()
|
||||||
|
|
||||||
// SetMemoryUtilization sets the relevant server metric.
|
// SetMemoryUtilization sets the memory utilization server metric. Must be
|
||||||
|
// in the range [0, 1].
|
||||||
SetMemoryUtilization(float64)
|
SetMemoryUtilization(float64)
|
||||||
// DeleteMemoryUtilization deletes the relevant server metric to prevent it
|
// DeleteMemoryUtilization deletes the memory utiliztion server metric to
|
||||||
// from being sent.
|
// prevent it from being sent.
|
||||||
DeleteMemoryUtilization()
|
DeleteMemoryUtilization()
|
||||||
|
|
||||||
// SetQPS sets the relevant server metric.
|
// SetApplicationUtilization sets the application utilization server
|
||||||
|
// metric. Must be greater than zero.
|
||||||
|
SetApplicationUtilization(float64)
|
||||||
|
// DeleteApplicationUtilization deletes the application utilization server
|
||||||
|
// metric to prevent it from being sent.
|
||||||
|
DeleteApplicationUtilization()
|
||||||
|
|
||||||
|
// SetQPS sets the Queries Per Second server metric. Must be greater than
|
||||||
|
// zero.
|
||||||
SetQPS(float64)
|
SetQPS(float64)
|
||||||
// DeleteQPS deletes the relevant server metric to prevent it from being
|
// DeleteQPS deletes the Queries Per Second server metric to prevent it
|
||||||
// sent.
|
// from being sent.
|
||||||
DeleteQPS()
|
DeleteQPS()
|
||||||
|
|
||||||
// SetEPS sets the relevant server metric.
|
// SetEPS sets the Errors Per Second server metric. Must be greater than
|
||||||
|
// zero.
|
||||||
SetEPS(float64)
|
SetEPS(float64)
|
||||||
// DeleteEPS deletes the relevant server metric to prevent it from being
|
// DeleteEPS deletes the Errors Per Second server metric to prevent it from
|
||||||
// sent.
|
// being sent.
|
||||||
DeleteEPS()
|
DeleteEPS()
|
||||||
|
|
||||||
// SetNamedUtilization sets the relevant server metric.
|
// SetNamedUtilization sets the named utilization server metric for the
|
||||||
|
// name provided. val must be in the range [0, 1].
|
||||||
SetNamedUtilization(name string, val float64)
|
SetNamedUtilization(name string, val float64)
|
||||||
// DeleteNamedUtilization deletes the relevant server metric to prevent it
|
// DeleteNamedUtilization deletes the named utilization server metric for
|
||||||
// from being sent.
|
// the name provided to prevent it from being sent.
|
||||||
DeleteNamedUtilization(name string)
|
DeleteNamedUtilization(name string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +158,7 @@ func newServerMetricsRecorder() *serverMetricsRecorder {
|
||||||
state: &ServerMetrics{
|
state: &ServerMetrics{
|
||||||
CPUUtilization: -1,
|
CPUUtilization: -1,
|
||||||
MemUtilization: -1,
|
MemUtilization: -1,
|
||||||
|
AppUtilization: -1,
|
||||||
QPS: -1,
|
QPS: -1,
|
||||||
EPS: -1,
|
EPS: -1,
|
||||||
Utilization: make(map[string]float64),
|
Utilization: make(map[string]float64),
|
||||||
|
@ -155,6 +175,7 @@ func (s *serverMetricsRecorder) ServerMetrics() *ServerMetrics {
|
||||||
return &ServerMetrics{
|
return &ServerMetrics{
|
||||||
CPUUtilization: s.state.CPUUtilization,
|
CPUUtilization: s.state.CPUUtilization,
|
||||||
MemUtilization: s.state.MemUtilization,
|
MemUtilization: s.state.MemUtilization,
|
||||||
|
AppUtilization: s.state.AppUtilization,
|
||||||
QPS: s.state.QPS,
|
QPS: s.state.QPS,
|
||||||
EPS: s.state.EPS,
|
EPS: s.state.EPS,
|
||||||
Utilization: copyMap(s.state.Utilization),
|
Utilization: copyMap(s.state.Utilization),
|
||||||
|
@ -173,6 +194,12 @@ func copyMap(m map[string]float64) map[string]float64 {
|
||||||
|
|
||||||
// SetCPUUtilization records a measurement for the CPU utilization metric.
|
// SetCPUUtilization records a measurement for the CPU utilization metric.
|
||||||
func (s *serverMetricsRecorder) SetCPUUtilization(val float64) {
|
func (s *serverMetricsRecorder) SetCPUUtilization(val float64) {
|
||||||
|
if val < 0 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring CPU Utilization value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.state.CPUUtilization = val
|
s.state.CPUUtilization = val
|
||||||
|
@ -181,11 +208,19 @@ func (s *serverMetricsRecorder) SetCPUUtilization(val float64) {
|
||||||
// DeleteCPUUtilization deletes the relevant server metric to prevent it from
|
// DeleteCPUUtilization deletes the relevant server metric to prevent it from
|
||||||
// being sent.
|
// being sent.
|
||||||
func (s *serverMetricsRecorder) DeleteCPUUtilization() {
|
func (s *serverMetricsRecorder) DeleteCPUUtilization() {
|
||||||
s.SetCPUUtilization(-1)
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.CPUUtilization = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMemoryUtilization records a measurement for the memory utilization metric.
|
// SetMemoryUtilization records a measurement for the memory utilization metric.
|
||||||
func (s *serverMetricsRecorder) SetMemoryUtilization(val float64) {
|
func (s *serverMetricsRecorder) SetMemoryUtilization(val float64) {
|
||||||
|
if val < 0 || val > 1 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring Memory Utilization value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.state.MemUtilization = val
|
s.state.MemUtilization = val
|
||||||
|
@ -194,11 +229,41 @@ func (s *serverMetricsRecorder) SetMemoryUtilization(val float64) {
|
||||||
// DeleteMemoryUtilization deletes the relevant server metric to prevent it
|
// DeleteMemoryUtilization deletes the relevant server metric to prevent it
|
||||||
// from being sent.
|
// from being sent.
|
||||||
func (s *serverMetricsRecorder) DeleteMemoryUtilization() {
|
func (s *serverMetricsRecorder) DeleteMemoryUtilization() {
|
||||||
s.SetMemoryUtilization(-1)
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.MemUtilization = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetApplicationUtilization records a measurement for a generic utilization
|
||||||
|
// metric.
|
||||||
|
func (s *serverMetricsRecorder) SetApplicationUtilization(val float64) {
|
||||||
|
if val < 0 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring Application Utilization value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.AppUtilization = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteApplicationUtilization deletes the relevant server metric to prevent
|
||||||
|
// it from being sent.
|
||||||
|
func (s *serverMetricsRecorder) DeleteApplicationUtilization() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.AppUtilization = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQPS records a measurement for the QPS metric.
|
// SetQPS records a measurement for the QPS metric.
|
||||||
func (s *serverMetricsRecorder) SetQPS(val float64) {
|
func (s *serverMetricsRecorder) SetQPS(val float64) {
|
||||||
|
if val < 0 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring QPS value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.state.QPS = val
|
s.state.QPS = val
|
||||||
|
@ -206,11 +271,19 @@ func (s *serverMetricsRecorder) SetQPS(val float64) {
|
||||||
|
|
||||||
// DeleteQPS deletes the relevant server metric to prevent it from being sent.
|
// DeleteQPS deletes the relevant server metric to prevent it from being sent.
|
||||||
func (s *serverMetricsRecorder) DeleteQPS() {
|
func (s *serverMetricsRecorder) DeleteQPS() {
|
||||||
s.SetQPS(-1)
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.QPS = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEPS records a measurement for the EPS metric.
|
// SetEPS records a measurement for the EPS metric.
|
||||||
func (s *serverMetricsRecorder) SetEPS(val float64) {
|
func (s *serverMetricsRecorder) SetEPS(val float64) {
|
||||||
|
if val < 0 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring EPS value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.state.EPS = val
|
s.state.EPS = val
|
||||||
|
@ -218,12 +291,20 @@ func (s *serverMetricsRecorder) SetEPS(val float64) {
|
||||||
|
|
||||||
// DeleteEPS deletes the relevant server metric to prevent it from being sent.
|
// DeleteEPS deletes the relevant server metric to prevent it from being sent.
|
||||||
func (s *serverMetricsRecorder) DeleteEPS() {
|
func (s *serverMetricsRecorder) DeleteEPS() {
|
||||||
s.SetEPS(-1)
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.state.EPS = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNamedUtilization records a measurement for a utilization metric uniquely
|
// SetNamedUtilization records a measurement for a utilization metric uniquely
|
||||||
// identifiable by name.
|
// identifiable by name.
|
||||||
func (s *serverMetricsRecorder) SetNamedUtilization(name string, val float64) {
|
func (s *serverMetricsRecorder) SetNamedUtilization(name string, val float64) {
|
||||||
|
if val < 0 || val > 1 {
|
||||||
|
if logger.V(2) {
|
||||||
|
logger.Infof("Ignoring Named Utilization value out of range: %v", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.state.Utilization[name] = val
|
s.state.Utilization[name] = val
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2023 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 orca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type s struct {
|
||||||
|
grpctest.Tester
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
grpctest.RunSubTests(t, s{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestServerMetrics_Setters(t *testing.T) {
|
||||||
|
smr := NewServerMetricsRecorder()
|
||||||
|
|
||||||
|
smr.SetCPUUtilization(0.1)
|
||||||
|
smr.SetMemoryUtilization(0.2)
|
||||||
|
smr.SetApplicationUtilization(0.3)
|
||||||
|
smr.SetQPS(0.4)
|
||||||
|
smr.SetEPS(0.5)
|
||||||
|
smr.SetNamedUtilization("x", 0.6)
|
||||||
|
|
||||||
|
want := &ServerMetrics{
|
||||||
|
CPUUtilization: 0.1,
|
||||||
|
MemUtilization: 0.2,
|
||||||
|
AppUtilization: 0.3,
|
||||||
|
QPS: 0.4,
|
||||||
|
EPS: 0.5,
|
||||||
|
Utilization: map[string]float64{"x": 0.6},
|
||||||
|
NamedMetrics: map[string]float64{},
|
||||||
|
RequestCost: map[string]float64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := smr.ServerMetrics()
|
||||||
|
if d := cmp.Diff(got, want); d != "" {
|
||||||
|
t.Fatalf("unexpected server metrics: -got +want: %v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestServerMetrics_Deleters(t *testing.T) {
|
||||||
|
smr := NewServerMetricsRecorder()
|
||||||
|
|
||||||
|
smr.SetCPUUtilization(0.1)
|
||||||
|
smr.SetMemoryUtilization(0.2)
|
||||||
|
smr.SetApplicationUtilization(0.3)
|
||||||
|
smr.SetQPS(0.4)
|
||||||
|
smr.SetEPS(0.5)
|
||||||
|
smr.SetNamedUtilization("x", 0.6)
|
||||||
|
smr.SetNamedUtilization("y", 0.7)
|
||||||
|
|
||||||
|
// Now delete everything except named_utilization "y".
|
||||||
|
smr.DeleteCPUUtilization()
|
||||||
|
smr.DeleteMemoryUtilization()
|
||||||
|
smr.DeleteApplicationUtilization()
|
||||||
|
smr.DeleteQPS()
|
||||||
|
smr.DeleteEPS()
|
||||||
|
smr.DeleteNamedUtilization("x")
|
||||||
|
|
||||||
|
want := &ServerMetrics{
|
||||||
|
CPUUtilization: -1,
|
||||||
|
MemUtilization: -1,
|
||||||
|
AppUtilization: -1,
|
||||||
|
QPS: -1,
|
||||||
|
EPS: -1,
|
||||||
|
Utilization: map[string]float64{"y": 0.7},
|
||||||
|
NamedMetrics: map[string]float64{},
|
||||||
|
RequestCost: map[string]float64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := smr.ServerMetrics()
|
||||||
|
if d := cmp.Diff(got, want); d != "" {
|
||||||
|
t.Fatalf("unexpected server metrics: -got +want: %v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestServerMetrics_Setters_Range(t *testing.T) {
|
||||||
|
smr := NewServerMetricsRecorder()
|
||||||
|
|
||||||
|
smr.SetCPUUtilization(0.1)
|
||||||
|
smr.SetMemoryUtilization(0.2)
|
||||||
|
smr.SetApplicationUtilization(0.3)
|
||||||
|
smr.SetQPS(0.4)
|
||||||
|
smr.SetEPS(0.5)
|
||||||
|
smr.SetNamedUtilization("x", 0.6)
|
||||||
|
|
||||||
|
// Negatives for all these fields should be ignored.
|
||||||
|
smr.SetCPUUtilization(-2)
|
||||||
|
smr.SetMemoryUtilization(-3)
|
||||||
|
smr.SetApplicationUtilization(-4)
|
||||||
|
smr.SetQPS(-0.1)
|
||||||
|
smr.SetEPS(-0.6)
|
||||||
|
smr.SetNamedUtilization("x", -2)
|
||||||
|
|
||||||
|
// Memory and named utilizations over 1 are ignored.
|
||||||
|
smr.SetMemoryUtilization(1.1)
|
||||||
|
smr.SetNamedUtilization("x", 1.1)
|
||||||
|
|
||||||
|
want := &ServerMetrics{
|
||||||
|
CPUUtilization: 0.1,
|
||||||
|
MemUtilization: 0.2,
|
||||||
|
AppUtilization: 0.3,
|
||||||
|
QPS: 0.4,
|
||||||
|
EPS: 0.5,
|
||||||
|
Utilization: map[string]float64{"x": 0.6},
|
||||||
|
NamedMetrics: map[string]float64{},
|
||||||
|
RequestCost: map[string]float64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := smr.ServerMetrics()
|
||||||
|
if d := cmp.Diff(got, want); d != "" {
|
||||||
|
t.Fatalf("unexpected server metrics: -got +want: %v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s) TestServerMetrics_Merge(t *testing.T) {
|
||||||
|
sm1 := &ServerMetrics{
|
||||||
|
CPUUtilization: 0.1,
|
||||||
|
MemUtilization: 0.2,
|
||||||
|
AppUtilization: 0.3,
|
||||||
|
QPS: -1,
|
||||||
|
EPS: 0,
|
||||||
|
Utilization: map[string]float64{"x": 0.6},
|
||||||
|
NamedMetrics: map[string]float64{"y": 0.2},
|
||||||
|
RequestCost: map[string]float64{"a": 0.1},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm2 := &ServerMetrics{
|
||||||
|
CPUUtilization: -1,
|
||||||
|
AppUtilization: 0,
|
||||||
|
QPS: 0.9,
|
||||||
|
EPS: 20,
|
||||||
|
Utilization: map[string]float64{"x": 0.5, "y": 0.4},
|
||||||
|
NamedMetrics: map[string]float64{"x": 0.1},
|
||||||
|
RequestCost: map[string]float64{"a": 0.2},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &ServerMetrics{
|
||||||
|
CPUUtilization: 0.1,
|
||||||
|
MemUtilization: 0,
|
||||||
|
AppUtilization: 0,
|
||||||
|
QPS: 0.9,
|
||||||
|
EPS: 20,
|
||||||
|
Utilization: map[string]float64{"x": 0.5, "y": 0.4},
|
||||||
|
NamedMetrics: map[string]float64{"x": 0.1, "y": 0.2},
|
||||||
|
RequestCost: map[string]float64{"a": 0.2},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm1.merge(sm2)
|
||||||
|
if d := cmp.Diff(sm1, want); d != "" {
|
||||||
|
t.Fatalf("unexpected server metrics: -got +want: %v", d)
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,9 +60,10 @@ func (t *testServiceImpl) UnaryCall(context.Context, *testpb.SimpleRequest) (*te
|
||||||
t.requests++
|
t.requests++
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
|
|
||||||
t.smr.SetNamedUtilization(requestsMetricKey, float64(t.requests))
|
t.smr.SetNamedUtilization(requestsMetricKey, float64(t.requests)*0.01)
|
||||||
t.smr.SetCPUUtilization(50.0)
|
t.smr.SetCPUUtilization(50.0)
|
||||||
t.smr.SetMemoryUtilization(99.0)
|
t.smr.SetMemoryUtilization(0.9)
|
||||||
|
t.smr.SetApplicationUtilization(1.2)
|
||||||
return &testpb.SimpleResponse{}, nil
|
return &testpb.SimpleResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ func (t *testServiceImpl) EmptyCall(context.Context, *testpb.Empty) (*testpb.Emp
|
||||||
t.smr.DeleteNamedUtilization(requestsMetricKey)
|
t.smr.DeleteNamedUtilization(requestsMetricKey)
|
||||||
t.smr.SetCPUUtilization(0)
|
t.smr.SetCPUUtilization(0)
|
||||||
t.smr.SetMemoryUtilization(0)
|
t.smr.SetMemoryUtilization(0)
|
||||||
|
t.smr.DeleteApplicationUtilization()
|
||||||
return &testpb.Empty{}, nil
|
return &testpb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +152,10 @@ func (s) TestE2E_CustomBackendMetrics_OutOfBand(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
wantProto := &v3orcapb.OrcaLoadReport{
|
wantProto := &v3orcapb.OrcaLoadReport{
|
||||||
CpuUtilization: 50.0,
|
CpuUtilization: 50.0,
|
||||||
MemUtilization: 99.0,
|
MemUtilization: 0.9,
|
||||||
Utilization: map[string]float64{requestsMetricKey: numRequests},
|
ApplicationUtilization: 1.2,
|
||||||
|
Utilization: map[string]float64{requestsMetricKey: numRequests * 0.01},
|
||||||
}
|
}
|
||||||
gotProto, err := stream.Recv()
|
gotProto, err := stream.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -622,7 +622,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2023 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 test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/base"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/internal/balancer/stub"
|
||||||
|
"google.golang.org/grpc/internal/stubserver"
|
||||||
|
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tsccPicker struct {
|
||||||
|
sc balancer.SubConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tsccPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
|
||||||
|
return balancer.PickResult{SubConn: p.sc}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSubConnEmpty tests that removing all addresses from a SubConn and then
|
||||||
|
// re-adding them does not cause a panic and properly reconnects.
|
||||||
|
func (s) TestSubConnEmpty(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// sc is the one SubConn used throughout the test. Created on demand and
|
||||||
|
// re-used on every update.
|
||||||
|
var sc balancer.SubConn
|
||||||
|
|
||||||
|
// Simple custom balancer that sets the address list to empty if the
|
||||||
|
// resolver produces no addresses. Pickfirst, by default, will remove the
|
||||||
|
// SubConn in this case instead.
|
||||||
|
bal := stub.BalancerFuncs{
|
||||||
|
UpdateClientConnState: func(d *stub.BalancerData, ccs balancer.ClientConnState) error {
|
||||||
|
if sc == nil {
|
||||||
|
var err error
|
||||||
|
sc, err = d.ClientConn.NewSubConn(ccs.ResolverState.Addresses, balancer.NewSubConnOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error creating initial subconn: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.ClientConn.UpdateAddresses(sc, ccs.ResolverState.Addresses)
|
||||||
|
}
|
||||||
|
sc.Connect()
|
||||||
|
|
||||||
|
if len(ccs.ResolverState.Addresses) == 0 {
|
||||||
|
d.ClientConn.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: base.NewErrPicker(errors.New("no addresses")),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
d.ClientConn.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Connecting,
|
||||||
|
Picker: &tsccPicker{sc: sc},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
UpdateSubConnState: func(d *stub.BalancerData, sc balancer.SubConn, scs balancer.SubConnState) {
|
||||||
|
switch scs.ConnectivityState {
|
||||||
|
case connectivity.Ready:
|
||||||
|
d.ClientConn.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.Ready,
|
||||||
|
Picker: &tsccPicker{sc: sc},
|
||||||
|
})
|
||||||
|
case connectivity.TransientFailure:
|
||||||
|
d.ClientConn.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: base.NewErrPicker(fmt.Errorf("error connecting: %v", scs.ConnectionError)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
stub.Register("tscc", bal)
|
||||||
|
|
||||||
|
// Start the stub server with our stub balancer.
|
||||||
|
ss := &stubserver.StubServer{
|
||||||
|
EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
|
||||||
|
return &testpb.Empty{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := ss.Start(nil, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"tscc":{}}]}`)); err != nil {
|
||||||
|
t.Fatalf("Error starting server: %v", err)
|
||||||
|
}
|
||||||
|
defer ss.Stop()
|
||||||
|
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
|
||||||
|
t.Fatalf("EmptyCall failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Removing addresses from resolver and SubConn")
|
||||||
|
ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{}})
|
||||||
|
awaitState(ctx, t, ss.CC, connectivity.TransientFailure)
|
||||||
|
|
||||||
|
t.Log("Re-adding addresses to resolver and SubConn")
|
||||||
|
ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ss.Address}}})
|
||||||
|
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
|
||||||
|
t.Fatalf("EmptyCall failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,9 +90,9 @@ func (s) TestOutlierDetection_NoopConfig(t *testing.T) {
|
||||||
// clientResourcesMultipleBackendsAndOD returns xDS resources which correspond
|
// clientResourcesMultipleBackendsAndOD returns xDS resources which correspond
|
||||||
// to multiple upstreams, corresponding different backends listening on
|
// to multiple upstreams, corresponding different backends listening on
|
||||||
// different localhost:port combinations. The resources also configure an
|
// different localhost:port combinations. The resources also configure an
|
||||||
// Outlier Detection Balancer set up with Failure Percentage Algorithm, which
|
// Outlier Detection Balancer configured through the passed in Outlier Detection
|
||||||
// ejects endpoints based on failure rate.
|
// proto.
|
||||||
func clientResourcesMultipleBackendsAndOD(params e2e.ResourceParams, ports []uint32) e2e.UpdateOptions {
|
func clientResourcesMultipleBackendsAndOD(params e2e.ResourceParams, ports []uint32, od *v3clusterpb.OutlierDetection) e2e.UpdateOptions {
|
||||||
routeConfigName := "route-" + params.DialTarget
|
routeConfigName := "route-" + params.DialTarget
|
||||||
clusterName := "cluster-" + params.DialTarget
|
clusterName := "cluster-" + params.DialTarget
|
||||||
endpointsName := "endpoints-" + params.DialTarget
|
endpointsName := "endpoints-" + params.DialTarget
|
||||||
|
@ -100,23 +100,14 @@ func clientResourcesMultipleBackendsAndOD(params e2e.ResourceParams, ports []uin
|
||||||
NodeID: params.NodeID,
|
NodeID: params.NodeID,
|
||||||
Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(params.DialTarget, routeConfigName)},
|
Listeners: []*v3listenerpb.Listener{e2e.DefaultClientListener(params.DialTarget, routeConfigName)},
|
||||||
Routes: []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, params.DialTarget, clusterName)},
|
Routes: []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, params.DialTarget, clusterName)},
|
||||||
Clusters: []*v3clusterpb.Cluster{clusterWithOutlierDetection(clusterName, endpointsName, params.SecLevel)},
|
Clusters: []*v3clusterpb.Cluster{clusterWithOutlierDetection(clusterName, endpointsName, params.SecLevel, od)},
|
||||||
Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(endpointsName, params.Host, ports)},
|
Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(endpointsName, params.Host, ports)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clusterWithOutlierDetection(clusterName, edsServiceName string, secLevel e2e.SecurityLevel) *v3clusterpb.Cluster {
|
func clusterWithOutlierDetection(clusterName, edsServiceName string, secLevel e2e.SecurityLevel, od *v3clusterpb.OutlierDetection) *v3clusterpb.Cluster {
|
||||||
cluster := e2e.DefaultCluster(clusterName, edsServiceName, secLevel)
|
cluster := e2e.DefaultCluster(clusterName, edsServiceName, secLevel)
|
||||||
cluster.OutlierDetection = &v3clusterpb.OutlierDetection{
|
cluster.OutlierDetection = od
|
||||||
Interval: &durationpb.Duration{Nanos: 50000000}, // .5 seconds
|
|
||||||
BaseEjectionTime: &durationpb.Duration{Seconds: 30},
|
|
||||||
MaxEjectionTime: &durationpb.Duration{Seconds: 300},
|
|
||||||
MaxEjectionPercent: &wrapperspb.UInt32Value{Value: 1},
|
|
||||||
FailurePercentageThreshold: &wrapperspb.UInt32Value{Value: 50},
|
|
||||||
EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 100},
|
|
||||||
FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 8},
|
|
||||||
FailurePercentageMinimumHosts: &wrapperspb.UInt32Value{Value: 3},
|
|
||||||
}
|
|
||||||
return cluster
|
return cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +188,103 @@ func (s) TestOutlierDetectionWithOutlier(t *testing.T) {
|
||||||
NodeID: nodeID,
|
NodeID: nodeID,
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
SecLevel: e2e.SecurityLevelNone,
|
SecLevel: e2e.SecurityLevelNone,
|
||||||
}, []uint32{port1, port2, port3})
|
}, []uint32{port1, port2, port3}, &v3clusterpb.OutlierDetection{
|
||||||
|
Interval: &durationpb.Duration{Nanos: 50000000}, // .5 seconds
|
||||||
|
BaseEjectionTime: &durationpb.Duration{Seconds: 30},
|
||||||
|
MaxEjectionTime: &durationpb.Duration{Seconds: 300},
|
||||||
|
MaxEjectionPercent: &wrapperspb.UInt32Value{Value: 1},
|
||||||
|
FailurePercentageThreshold: &wrapperspb.UInt32Value{Value: 50},
|
||||||
|
EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 100},
|
||||||
|
FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 8},
|
||||||
|
FailurePercentageMinimumHosts: &wrapperspb.UInt32Value{Value: 3},
|
||||||
|
})
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := managementServer.Update(ctx, resources); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to dial local test server: %v", err)
|
||||||
|
}
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
client := testgrpc.NewTestServiceClient(cc)
|
||||||
|
|
||||||
|
fullAddresses := []resolver.Address{
|
||||||
|
{Addr: backend1.Address},
|
||||||
|
{Addr: backend2.Address},
|
||||||
|
{Addr: backend3.Address},
|
||||||
|
}
|
||||||
|
// At first, due to no statistics on each of the backends, the 3
|
||||||
|
// upstreams should all be round robined across.
|
||||||
|
if err = checkRoundRobinRPCs(ctx, client, fullAddresses); err != nil {
|
||||||
|
t.Fatalf("error in expected round robin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The addresses which don't return errors.
|
||||||
|
okAddresses := []resolver.Address{
|
||||||
|
{Addr: backend1.Address},
|
||||||
|
{Addr: backend2.Address},
|
||||||
|
}
|
||||||
|
// After calling the three upstreams, one of them constantly error
|
||||||
|
// and should eventually be ejected for a period of time. This
|
||||||
|
// period of time should cause the RPC's to be round robined only
|
||||||
|
// across the two that are healthy.
|
||||||
|
if err = checkRoundRobinRPCs(ctx, client, okAddresses); err != nil {
|
||||||
|
t.Fatalf("error in expected round robin: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOutlierDetectionXDSDefaultOn tests that Outlier Detection is by default
|
||||||
|
// configured on in the xDS Flow. If the Outlier Detection proto message is
|
||||||
|
// present with SuccessRateEjection unset, then Outlier Detection should be
|
||||||
|
// turned on. The test setups and xDS system with xDS resources with Outlier
|
||||||
|
// Detection present in the CDS update, but with SuccessRateEjection unset, and
|
||||||
|
// asserts that Outlier Detection is turned on and ejects upstreams.
|
||||||
|
func (s) TestOutlierDetectionXDSDefaultOn(t *testing.T) {
|
||||||
|
managementServer, nodeID, _, r, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{})
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Working backend 1.
|
||||||
|
backend1 := stubserver.StartTestService(t, nil)
|
||||||
|
port1 := testutils.ParsePort(t, backend1.Address)
|
||||||
|
defer backend1.Stop()
|
||||||
|
|
||||||
|
// Working backend 2.
|
||||||
|
backend2 := stubserver.StartTestService(t, nil)
|
||||||
|
port2 := testutils.ParsePort(t, backend2.Address)
|
||||||
|
defer backend2.Stop()
|
||||||
|
|
||||||
|
// Backend 3 that will always return an error and eventually ejected.
|
||||||
|
backend3 := stubserver.StartTestService(t, &stubserver.StubServer{
|
||||||
|
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { return nil, errors.New("some error") },
|
||||||
|
})
|
||||||
|
port3 := testutils.ParsePort(t, backend3.Address)
|
||||||
|
defer backend3.Stop()
|
||||||
|
|
||||||
|
// Configure CDS resources with Outlier Detection set but
|
||||||
|
// EnforcingSuccessRate unset. This should cause Outlier Detection to be
|
||||||
|
// configured with SuccessRateEjection present in configuration, which will
|
||||||
|
// eventually be populated with its default values along with the knobs set
|
||||||
|
// as SuccessRate fields in the proto, and thus Outlier Detection should be
|
||||||
|
// on and actively eject upstreams.
|
||||||
|
const serviceName = "my-service-client-side-xds"
|
||||||
|
resources := clientResourcesMultipleBackendsAndOD(e2e.ResourceParams{
|
||||||
|
DialTarget: serviceName,
|
||||||
|
NodeID: nodeID,
|
||||||
|
Host: "localhost",
|
||||||
|
SecLevel: e2e.SecurityLevelNone,
|
||||||
|
}, []uint32{port1, port2, port3}, &v3clusterpb.OutlierDetection{
|
||||||
|
// Need to set knobs to trigger ejection within the test time frame.
|
||||||
|
Interval: &durationpb.Duration{Nanos: 50000000},
|
||||||
|
// EnforcingSuccessRateSet to nil, causes success rate algorithm to be
|
||||||
|
// turned on.
|
||||||
|
SuccessRateMinimumHosts: &wrapperspb.UInt32Value{Value: 1},
|
||||||
|
SuccessRateRequestVolume: &wrapperspb.UInt32Value{Value: 8},
|
||||||
|
SuccessRateStdevFactor: &wrapperspb.UInt32Value{Value: 1},
|
||||||
|
})
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := managementServer.Update(ctx, resources); err != nil {
|
if err := managementServer.Update(ctx, resources); err != nil {
|
||||||
|
|
|
@ -19,4 +19,4 @@
|
||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
// Version is the current grpc version.
|
// Version is the current grpc version.
|
||||||
const Version = "1.56.0-dev"
|
const Version = "1.56.1"
|
||||||
|
|
|
@ -27,17 +27,16 @@ import (
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||||
|
"google.golang.org/grpc/internal/balancer/nop"
|
||||||
"google.golang.org/grpc/internal/buffer"
|
"google.golang.org/grpc/internal/buffer"
|
||||||
xdsinternal "google.golang.org/grpc/internal/credentials/xds"
|
xdsinternal "google.golang.org/grpc/internal/credentials/xds"
|
||||||
"google.golang.org/grpc/internal/envconfig"
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
"google.golang.org/grpc/internal/grpcsync"
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
"google.golang.org/grpc/internal/pretty"
|
"google.golang.org/grpc/internal/pretty"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/clusterresolver"
|
"google.golang.org/grpc/xds/internal/balancer/clusterresolver"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
|
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient"
|
"google.golang.org/grpc/xds/internal/xdsclient"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
|
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
|
||||||
)
|
)
|
||||||
|
@ -75,11 +74,25 @@ type bb struct{}
|
||||||
|
|
||||||
// Build creates a new CDS balancer with the ClientConn.
|
// Build creates a new CDS balancer with the ClientConn.
|
||||||
func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
|
func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
|
||||||
|
builder := balancer.Get(clusterresolver.Name)
|
||||||
|
if builder == nil {
|
||||||
|
// Shouldn't happen, registered through imported Cluster Resolver,
|
||||||
|
// defensive programming.
|
||||||
|
logger.Errorf("%q LB policy is needed but not registered", clusterresolver.Name)
|
||||||
|
return nop.NewBalancer(cc, fmt.Errorf("%q LB policy is needed but not registered", clusterresolver.Name))
|
||||||
|
}
|
||||||
|
crParser, ok := builder.(balancer.ConfigParser)
|
||||||
|
if !ok {
|
||||||
|
// Shouldn't happen, imported Cluster Resolver builder has this method.
|
||||||
|
logger.Errorf("%q LB policy does not implement a config parser", clusterresolver.Name)
|
||||||
|
return nop.NewBalancer(cc, fmt.Errorf("%q LB policy does not implement a config parser", clusterresolver.Name))
|
||||||
|
}
|
||||||
b := &cdsBalancer{
|
b := &cdsBalancer{
|
||||||
bOpts: opts,
|
bOpts: opts,
|
||||||
updateCh: buffer.NewUnbounded(),
|
updateCh: buffer.NewUnbounded(),
|
||||||
closed: grpcsync.NewEvent(),
|
closed: grpcsync.NewEvent(),
|
||||||
done: grpcsync.NewEvent(),
|
done: grpcsync.NewEvent(),
|
||||||
|
crParser: crParser,
|
||||||
xdsHI: xdsinternal.NewHandshakeInfo(nil, nil),
|
xdsHI: xdsinternal.NewHandshakeInfo(nil, nil),
|
||||||
}
|
}
|
||||||
b.logger = prefixLogger((b))
|
b.logger = prefixLogger((b))
|
||||||
|
@ -160,6 +173,7 @@ type cdsBalancer struct {
|
||||||
logger *grpclog.PrefixLogger
|
logger *grpclog.PrefixLogger
|
||||||
closed *grpcsync.Event
|
closed *grpcsync.Event
|
||||||
done *grpcsync.Event
|
done *grpcsync.Event
|
||||||
|
crParser balancer.ConfigParser
|
||||||
|
|
||||||
// The certificate providers are cached here to that they can be closed when
|
// The certificate providers are cached here to that they can be closed when
|
||||||
// a new provider is to be created.
|
// a new provider is to be created.
|
||||||
|
@ -271,52 +285,6 @@ func buildProviderFunc(configs map[string]*certprovider.BuildableConfig, instanc
|
||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func outlierDetectionToConfig(od *xdsresource.OutlierDetection) outlierdetection.LBConfig { // Already validated - no need to return error
|
|
||||||
if od == nil {
|
|
||||||
// "If the outlier_detection field is not set in the Cluster message, a
|
|
||||||
// "no-op" outlier_detection config will be generated, with interval set
|
|
||||||
// to the maximum possible value and all other fields unset." - A50
|
|
||||||
return outlierdetection.LBConfig{
|
|
||||||
Interval: 1<<63 - 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "if the enforcing_success_rate field is set to 0, the config
|
|
||||||
// success_rate_ejection field will be null and all success_rate_* fields
|
|
||||||
// will be ignored." - A50
|
|
||||||
var sre *outlierdetection.SuccessRateEjection
|
|
||||||
if od.EnforcingSuccessRate != 0 {
|
|
||||||
sre = &outlierdetection.SuccessRateEjection{
|
|
||||||
StdevFactor: od.SuccessRateStdevFactor,
|
|
||||||
EnforcementPercentage: od.EnforcingSuccessRate,
|
|
||||||
MinimumHosts: od.SuccessRateMinimumHosts,
|
|
||||||
RequestVolume: od.SuccessRateRequestVolume,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "If the enforcing_failure_percent field is set to 0 or null, the config
|
|
||||||
// failure_percent_ejection field will be null and all failure_percent_*
|
|
||||||
// fields will be ignored." - A50
|
|
||||||
var fpe *outlierdetection.FailurePercentageEjection
|
|
||||||
if od.EnforcingFailurePercentage != 0 {
|
|
||||||
fpe = &outlierdetection.FailurePercentageEjection{
|
|
||||||
Threshold: od.FailurePercentageThreshold,
|
|
||||||
EnforcementPercentage: od.EnforcingFailurePercentage,
|
|
||||||
MinimumHosts: od.FailurePercentageMinimumHosts,
|
|
||||||
RequestVolume: od.FailurePercentageRequestVolume,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return outlierdetection.LBConfig{
|
|
||||||
Interval: internalserviceconfig.Duration(od.Interval),
|
|
||||||
BaseEjectionTime: internalserviceconfig.Duration(od.BaseEjectionTime),
|
|
||||||
MaxEjectionTime: internalserviceconfig.Duration(od.MaxEjectionTime),
|
|
||||||
MaxEjectionPercent: od.MaxEjectionPercent,
|
|
||||||
SuccessRateEjection: sre,
|
|
||||||
FailurePercentageEjection: fpe,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleWatchUpdate handles a watch update from the xDS Client. Good updates
|
// handleWatchUpdate handles a watch update from the xDS Client. Good updates
|
||||||
// lead to clientConn updates being invoked on the underlying cluster_resolver balancer.
|
// lead to clientConn updates being invoked on the underlying cluster_resolver balancer.
|
||||||
func (b *cdsBalancer) handleWatchUpdate(update clusterHandlerUpdate) {
|
func (b *cdsBalancer) handleWatchUpdate(update clusterHandlerUpdate) {
|
||||||
|
@ -390,28 +358,43 @@ func (b *cdsBalancer) handleWatchUpdate(update clusterHandlerUpdate) {
|
||||||
b.logger.Infof("Unexpected cluster type %v when handling update from cluster handler", cu.ClusterType)
|
b.logger.Infof("Unexpected cluster type %v when handling update from cluster handler", cu.ClusterType)
|
||||||
}
|
}
|
||||||
if envconfig.XDSOutlierDetection {
|
if envconfig.XDSOutlierDetection {
|
||||||
dms[i].OutlierDetection = outlierDetectionToConfig(cu.OutlierDetection)
|
odJSON := cu.OutlierDetection
|
||||||
|
// "In the cds LB policy, if the outlier_detection field is not set in
|
||||||
|
// the Cluster resource, a "no-op" outlier_detection config will be
|
||||||
|
// generated in the corresponding DiscoveryMechanism config, with all
|
||||||
|
// fields unset." - A50
|
||||||
|
if odJSON == nil {
|
||||||
|
// This will pick up top level defaults in Cluster Resolver
|
||||||
|
// ParseConfig, but sre and fpe will be nil still so still a
|
||||||
|
// "no-op" config.
|
||||||
|
odJSON = json.RawMessage(`{}`)
|
||||||
|
}
|
||||||
|
dms[i].OutlierDetection = odJSON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare Cluster Resolver config, marshal into JSON, and then Parse it to
|
||||||
|
// get configuration to send downward to Cluster Resolver.
|
||||||
lbCfg := &clusterresolver.LBConfig{
|
lbCfg := &clusterresolver.LBConfig{
|
||||||
DiscoveryMechanisms: dms,
|
DiscoveryMechanisms: dms,
|
||||||
|
XDSLBPolicy: update.lbPolicy,
|
||||||
}
|
}
|
||||||
|
crLBCfgJSON, err := json.Marshal(lbCfg)
|
||||||
bc := &internalserviceconfig.BalancerConfig{}
|
if err != nil {
|
||||||
if err := json.Unmarshal(update.lbPolicy, bc); err != nil {
|
// Shouldn't happen, since we just prepared struct.
|
||||||
// This will never occur, valid configuration is emitted from the xDS
|
b.logger.Errorf("cds_balancer: error marshalling prepared config: %v", lbCfg)
|
||||||
// Client. Validity is already checked in the xDS Client, however, this
|
|
||||||
// double validation is present because Unmarshalling and Validating are
|
|
||||||
// coupled into one json.Unmarshal operation). We will switch this in
|
|
||||||
// the future to two separate operations.
|
|
||||||
b.logger.Errorf("Emitted lbPolicy %s from xDS Client is invalid: %v", update.lbPolicy, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lbCfg.XDSLBPolicy = bc
|
|
||||||
|
var sc serviceconfig.LoadBalancingConfig
|
||||||
|
if sc, err = b.crParser.ParseConfig(crLBCfgJSON); err != nil {
|
||||||
|
b.logger.Errorf("cds_balancer: cluster_resolver config generated %v is invalid: %v", crLBCfgJSON, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ccState := balancer.ClientConnState{
|
ccState := balancer.ClientConnState{
|
||||||
ResolverState: xdsclient.SetClient(resolver.State{}, b.xdsClient),
|
ResolverState: xdsclient.SetClient(resolver.State{}, b.xdsClient),
|
||||||
BalancerConfig: lbCfg,
|
BalancerConfig: sc,
|
||||||
}
|
}
|
||||||
if err := b.childLB.UpdateClientConnState(ccState); err != nil {
|
if err := b.childLB.UpdateClientConnState(ccState); err != nil {
|
||||||
b.logger.Errorf("Encountered error when sending config {%+v} to child policy: %v", ccState, err)
|
b.logger.Errorf("Encountered error when sending config {%+v} to child policy: %v", ccState, err)
|
||||||
|
|
|
@ -253,7 +253,7 @@ func (s) TestSecurityConfigWithoutXDSCreds(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -312,7 +312,7 @@ func (s) TestNoSecurityConfigWithXDSCreds(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -468,7 +468,7 @@ func (s) TestSecurityConfigUpdate_BadToGood(t *testing.T) {
|
||||||
// create a new EDS balancer. The fake EDS balancer created above will be
|
// create a new EDS balancer. The fake EDS balancer created above will be
|
||||||
// returned to the CDS balancer, because we have overridden the
|
// returned to the CDS balancer, because we have overridden the
|
||||||
// newChildBalancer function as part of test setup.
|
// newChildBalancer function as part of test setup.
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -502,7 +502,7 @@ func (s) TestGoodSecurityConfig(t *testing.T) {
|
||||||
// create a new EDS balancer. The fake EDS balancer created above will be
|
// create a new EDS balancer. The fake EDS balancer created above will be
|
||||||
// returned to the CDS balancer, because we have overridden the
|
// returned to the CDS balancer, because we have overridden the
|
||||||
// newChildBalancer function as part of test setup.
|
// newChildBalancer function as part of test setup.
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -555,7 +555,7 @@ func (s) TestSecurityConfigUpdate_GoodToFallback(t *testing.T) {
|
||||||
// create a new EDS balancer. The fake EDS balancer created above will be
|
// create a new EDS balancer. The fake EDS balancer created above will be
|
||||||
// returned to the CDS balancer, because we have overridden the
|
// returned to the CDS balancer, because we have overridden the
|
||||||
// newChildBalancer function as part of test setup.
|
// newChildBalancer function as part of test setup.
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -608,7 +608,7 @@ func (s) TestSecurityConfigUpdate_GoodToBad(t *testing.T) {
|
||||||
// create a new EDS balancer. The fake EDS balancer created above will be
|
// create a new EDS balancer. The fake EDS balancer created above will be
|
||||||
// returned to the CDS balancer, because we have overridden the
|
// returned to the CDS balancer, because we have overridden the
|
||||||
// newChildBalancer function as part of test setup.
|
// newChildBalancer function as part of test setup.
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -687,7 +687,7 @@ func (s) TestSecurityConfigUpdate_GoodToGood(t *testing.T) {
|
||||||
},
|
},
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
|
|
@ -58,9 +58,8 @@ var (
|
||||||
Type: "insecure",
|
Type: "insecure",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
noopODLBCfg = outlierdetection.LBConfig{
|
noopODLBCfg = outlierdetection.LBConfig{}
|
||||||
Interval: 1<<63 - 1,
|
noopODLBCfgJSON, _ = json.Marshal(noopODLBCfg)
|
||||||
}
|
|
||||||
wrrLocalityLBConfig = &internalserviceconfig.BalancerConfig{
|
wrrLocalityLBConfig = &internalserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
|
@ -166,7 +165,11 @@ func (tb *testEDSBalancer) waitForClientConnUpdate(ctx context.Context, wantCCS
|
||||||
if xdsclient.FromResolverState(gotCCS.ResolverState) == nil {
|
if xdsclient.FromResolverState(gotCCS.ResolverState) == nil {
|
||||||
return fmt.Errorf("want resolver state with XDSClient attached, got one without")
|
return fmt.Errorf("want resolver state with XDSClient attached, got one without")
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(gotCCS, wantCCS, cmpopts.IgnoreFields(resolver.State{}, "Attributes")); diff != "" {
|
|
||||||
|
// Calls into Cluster Resolver LB Config Equal(), which ignores JSON
|
||||||
|
// configuration but compares the Parsed Configuration of the JSON fields
|
||||||
|
// emitted from ParseConfig() on the cluster resolver.
|
||||||
|
if diff := cmp.Diff(gotCCS, wantCCS, cmpopts.IgnoreFields(resolver.State{}, "Attributes"), cmp.AllowUnexported(clusterresolver.LBConfig{})); diff != "" {
|
||||||
return fmt.Errorf("received unexpected ClientConnState, diff (-got +want): %v", diff)
|
return fmt.Errorf("received unexpected ClientConnState, diff (-got +want): %v", diff)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -229,9 +232,26 @@ func cdsCCS(cluster string, xdsC xdsclient.XDSClient) balancer.ClientConnState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// edsCCS is a helper function to construct a good update passed from the
|
// edsCCS is a helper function to construct a Client Conn update which
|
||||||
// cdsBalancer to the edsBalancer.
|
// represents what the CDS Balancer passes to the Cluster Resolver. It calls
|
||||||
func edsCCS(service string, countMax *uint32, enableLRS bool, xdslbpolicy *internalserviceconfig.BalancerConfig, odConfig outlierdetection.LBConfig) balancer.ClientConnState {
|
// into Cluster Resolver's ParseConfig to get the service config to fill out the
|
||||||
|
// Client Conn State. This is to fill out unexported parts of the Cluster
|
||||||
|
// Resolver config struct. Returns an empty Client Conn State if it encounters
|
||||||
|
// an error building out the Client Conn State.
|
||||||
|
func edsCCS(service string, countMax *uint32, enableLRS bool, xdslbpolicy json.RawMessage, odConfig json.RawMessage) balancer.ClientConnState {
|
||||||
|
builder := balancer.Get(clusterresolver.Name)
|
||||||
|
if builder == nil {
|
||||||
|
// Shouldn't happen, registered through imported Cluster Resolver,
|
||||||
|
// defensive programming.
|
||||||
|
logger.Errorf("%q LB policy is needed but not registered", clusterresolver.Name)
|
||||||
|
return balancer.ClientConnState{} // will fail the calling test eventually through error in diff.
|
||||||
|
}
|
||||||
|
crParser, ok := builder.(balancer.ConfigParser)
|
||||||
|
if !ok {
|
||||||
|
// Shouldn't happen, imported Cluster Resolver builder has this method.
|
||||||
|
logger.Errorf("%q LB policy does not implement a config parser", clusterresolver.Name)
|
||||||
|
return balancer.ClientConnState{}
|
||||||
|
}
|
||||||
discoveryMechanism := clusterresolver.DiscoveryMechanism{
|
discoveryMechanism := clusterresolver.DiscoveryMechanism{
|
||||||
Type: clusterresolver.DiscoveryMechanismTypeEDS,
|
Type: clusterresolver.DiscoveryMechanismTypeEDS,
|
||||||
Cluster: service,
|
Cluster: service,
|
||||||
|
@ -246,8 +266,21 @@ func edsCCS(service string, countMax *uint32, enableLRS bool, xdslbpolicy *inter
|
||||||
XDSLBPolicy: xdslbpolicy,
|
XDSLBPolicy: xdslbpolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crLBCfgJSON, err := json.Marshal(lbCfg)
|
||||||
|
if err != nil {
|
||||||
|
// Shouldn't happen, since we just prepared struct.
|
||||||
|
logger.Errorf("cds_balancer: error marshalling prepared config: %v", lbCfg)
|
||||||
|
return balancer.ClientConnState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sc serviceconfig.LoadBalancingConfig
|
||||||
|
if sc, err = crParser.ParseConfig(crLBCfgJSON); err != nil {
|
||||||
|
logger.Errorf("cds_balancer: cluster_resolver config generated %v is invalid: %v", crLBCfgJSON, err)
|
||||||
|
return balancer.ClientConnState{}
|
||||||
|
}
|
||||||
|
|
||||||
return balancer.ClientConnState{
|
return balancer.ClientConnState{
|
||||||
BalancerConfig: lbCfg,
|
BalancerConfig: sc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,7 +435,7 @@ func (s) TestHandleClusterUpdate(t *testing.T) {
|
||||||
LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
},
|
},
|
||||||
wantCCS: edsCCS(serviceName, nil, true, wrrLocalityLBConfig, noopODLBCfg),
|
wantCCS: edsCCS(serviceName, nil, true, wrrLocalityLBConfigJSON, noopODLBCfgJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy-case-without-lrs",
|
name: "happy-case-without-lrs",
|
||||||
|
@ -410,7 +443,7 @@ func (s) TestHandleClusterUpdate(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
},
|
},
|
||||||
wantCCS: edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg),
|
wantCCS: edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy-case-with-ring-hash-lb-policy",
|
name: "happy-case-with-ring-hash-lb-policy",
|
||||||
|
@ -418,49 +451,64 @@ func (s) TestHandleClusterUpdate(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: ringHashLBConfigJSON,
|
LBPolicy: ringHashLBConfigJSON,
|
||||||
},
|
},
|
||||||
wantCCS: edsCCS(serviceName, nil, false, &internalserviceconfig.BalancerConfig{
|
wantCCS: edsCCS(serviceName, nil, false, ringHashLBConfigJSON, noopODLBCfgJSON),
|
||||||
Name: ringhash.Name,
|
|
||||||
Config: &ringhash.LBConfig{MinRingSize: 10, MaxRingSize: 100},
|
|
||||||
}, noopODLBCfg),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "happy-case-outlier-detection",
|
name: "happy-case-outlier-detection-xds-defaults",
|
||||||
|
// i.e. od proto set but no proto fields set
|
||||||
cdsUpdate: xdsresource.ClusterUpdate{
|
cdsUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
OutlierDetection: &xdsresource.OutlierDetection{
|
OutlierDetection: json.RawMessage(`{
|
||||||
Interval: 10 * time.Second,
|
"successRateEjection": {}
|
||||||
BaseEjectionTime: 30 * time.Second,
|
}`),
|
||||||
MaxEjectionTime: 300 * time.Second,
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateStdevFactor: 1900,
|
|
||||||
EnforcingSuccessRate: 100,
|
|
||||||
SuccessRateMinimumHosts: 5,
|
|
||||||
SuccessRateRequestVolume: 100,
|
|
||||||
FailurePercentageThreshold: 85,
|
|
||||||
EnforcingFailurePercentage: 5,
|
|
||||||
FailurePercentageMinimumHosts: 5,
|
|
||||||
FailurePercentageRequestVolume: 50,
|
|
||||||
},
|
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
},
|
},
|
||||||
wantCCS: edsCCS(serviceName, nil, false, wrrLocalityLBConfig, outlierdetection.LBConfig{
|
wantCCS: edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, json.RawMessage(`{
|
||||||
Interval: internalserviceconfig.Duration(10 * time.Second),
|
"successRateEjection": {}
|
||||||
BaseEjectionTime: internalserviceconfig.Duration(30 * time.Second),
|
}`)),
|
||||||
MaxEjectionTime: internalserviceconfig.Duration(300 * time.Second),
|
},
|
||||||
MaxEjectionPercent: 10,
|
{
|
||||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
name: "happy-case-outlier-detection-all-fields-set",
|
||||||
StdevFactor: 1900,
|
cdsUpdate: xdsresource.ClusterUpdate{
|
||||||
EnforcementPercentage: 100,
|
ClusterName: serviceName,
|
||||||
MinimumHosts: 5,
|
OutlierDetection: json.RawMessage(`{
|
||||||
RequestVolume: 100,
|
"interval": "10s",
|
||||||
|
"baseEjectionTime": "30s",
|
||||||
|
"maxEjectionTime": "300s",
|
||||||
|
"maxEjectionPercent": 10,
|
||||||
|
"successRateEjection": {
|
||||||
|
"stdevFactor": 1900,
|
||||||
|
"enforcementPercentage": 100,
|
||||||
|
"minimumHosts": 5,
|
||||||
|
"requestVolume": 100
|
||||||
},
|
},
|
||||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
"failurePercentageEjection": {
|
||||||
Threshold: 85,
|
"threshold": 85,
|
||||||
EnforcementPercentage: 5,
|
"enforcementPercentage": 5,
|
||||||
MinimumHosts: 5,
|
"minimumHosts": 5,
|
||||||
RequestVolume: 50,
|
"requestVolume": 50
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
|
},
|
||||||
|
wantCCS: edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, json.RawMessage(`{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}`)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +579,7 @@ func (s) TestHandleClusterUpdateError(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -619,7 +667,7 @@ func (s) TestResolverError(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -671,7 +719,7 @@ func (s) TestUpdateSubConnState(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -709,7 +757,7 @@ func (s) TestCircuitBreaking(t *testing.T) {
|
||||||
MaxRequests: &maxRequests,
|
MaxRequests: &maxRequests,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(clusterName, &maxRequests, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(clusterName, &maxRequests, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -746,7 +794,7 @@ func (s) TestClose(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -820,7 +868,7 @@ func (s) TestExitIdle(t *testing.T) {
|
||||||
ClusterName: serviceName,
|
ClusterName: serviceName,
|
||||||
LBPolicy: wrrLocalityLBConfigJSON,
|
LBPolicy: wrrLocalityLBConfigJSON,
|
||||||
}
|
}
|
||||||
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfig, noopODLBCfg)
|
wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||||
defer ctxCancel()
|
defer ctxCancel()
|
||||||
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
|
||||||
|
@ -882,130 +930,3 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s) TestOutlierDetectionToConfig(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
od *xdsresource.OutlierDetection
|
|
||||||
odLBCfgWant outlierdetection.LBConfig
|
|
||||||
}{
|
|
||||||
// "if the outlier_detection field is not set in the Cluster resource,
|
|
||||||
// a "no-op" outlier_detection config will be generated in the
|
|
||||||
// corresponding DiscoveryMechanism config, with interval set to the
|
|
||||||
// maximum possible value and all other fields unset." - A50
|
|
||||||
{
|
|
||||||
name: "no-op-outlier-detection-config",
|
|
||||||
od: nil,
|
|
||||||
odLBCfgWant: noopODLBCfg,
|
|
||||||
},
|
|
||||||
// "if the enforcing_success_rate field is set to 0, the config
|
|
||||||
// success_rate_ejection field will be null and all success_rate_*
|
|
||||||
// fields will be ignored." - A50
|
|
||||||
{
|
|
||||||
name: "enforcing-success-rate-zero",
|
|
||||||
od: &xdsresource.OutlierDetection{
|
|
||||||
Interval: 10 * time.Second,
|
|
||||||
BaseEjectionTime: 30 * time.Second,
|
|
||||||
MaxEjectionTime: 300 * time.Second,
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateStdevFactor: 1900,
|
|
||||||
EnforcingSuccessRate: 0,
|
|
||||||
SuccessRateMinimumHosts: 5,
|
|
||||||
SuccessRateRequestVolume: 100,
|
|
||||||
FailurePercentageThreshold: 85,
|
|
||||||
EnforcingFailurePercentage: 5,
|
|
||||||
FailurePercentageMinimumHosts: 5,
|
|
||||||
FailurePercentageRequestVolume: 50,
|
|
||||||
},
|
|
||||||
odLBCfgWant: outlierdetection.LBConfig{
|
|
||||||
Interval: internalserviceconfig.Duration(10 * time.Second),
|
|
||||||
BaseEjectionTime: internalserviceconfig.Duration(30 * time.Second),
|
|
||||||
MaxEjectionTime: internalserviceconfig.Duration(300 * time.Second),
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateEjection: nil,
|
|
||||||
FailurePercentageEjection: &outlierdetection.FailurePercentageEjection{
|
|
||||||
Threshold: 85,
|
|
||||||
EnforcementPercentage: 5,
|
|
||||||
MinimumHosts: 5,
|
|
||||||
RequestVolume: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// "If the enforcing_failure_percent field is set to 0 or null, the
|
|
||||||
// config failure_percent_ejection field will be null and all
|
|
||||||
// failure_percent_* fields will be ignored." - A50
|
|
||||||
{
|
|
||||||
name: "enforcing-failure-percentage-zero",
|
|
||||||
od: &xdsresource.OutlierDetection{
|
|
||||||
Interval: 10 * time.Second,
|
|
||||||
BaseEjectionTime: 30 * time.Second,
|
|
||||||
MaxEjectionTime: 300 * time.Second,
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateStdevFactor: 1900,
|
|
||||||
EnforcingSuccessRate: 100,
|
|
||||||
SuccessRateMinimumHosts: 5,
|
|
||||||
SuccessRateRequestVolume: 100,
|
|
||||||
FailurePercentageThreshold: 85,
|
|
||||||
EnforcingFailurePercentage: 0,
|
|
||||||
FailurePercentageMinimumHosts: 5,
|
|
||||||
FailurePercentageRequestVolume: 50,
|
|
||||||
},
|
|
||||||
odLBCfgWant: outlierdetection.LBConfig{
|
|
||||||
Interval: internalserviceconfig.Duration(10 * time.Second),
|
|
||||||
BaseEjectionTime: internalserviceconfig.Duration(30 * time.Second),
|
|
||||||
MaxEjectionTime: internalserviceconfig.Duration(300 * time.Second),
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateEjection: &outlierdetection.SuccessRateEjection{
|
|
||||||
StdevFactor: 1900,
|
|
||||||
EnforcementPercentage: 100,
|
|
||||||
MinimumHosts: 5,
|
|
||||||
RequestVolume: 100,
|
|
||||||
},
|
|
||||||
FailurePercentageEjection: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "normal-conversion",
|
|
||||||
od: &xdsresource.OutlierDetection{
|
|
||||||
Interval: 10 * time.Second,
|
|
||||||
BaseEjectionTime: 30 * time.Second,
|
|
||||||
MaxEjectionTime: 300 * time.Second,
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateStdevFactor: 1900,
|
|
||||||
EnforcingSuccessRate: 100,
|
|
||||||
SuccessRateMinimumHosts: 5,
|
|
||||||
SuccessRateRequestVolume: 100,
|
|
||||||
FailurePercentageThreshold: 85,
|
|
||||||
EnforcingFailurePercentage: 5,
|
|
||||||
FailurePercentageMinimumHosts: 5,
|
|
||||||
FailurePercentageRequestVolume: 50,
|
|
||||||
},
|
|
||||||
odLBCfgWant: outlierdetection.LBConfig{
|
|
||||||
Interval: internalserviceconfig.Duration(10 * time.Second),
|
|
||||||
BaseEjectionTime: internalserviceconfig.Duration(30 * time.Second),
|
|
||||||
MaxEjectionTime: internalserviceconfig.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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
odLBCfgGot := outlierDetectionToConfig(test.od)
|
|
||||||
if diff := cmp.Diff(odLBCfgGot, test.odLBCfgWant); diff != "" {
|
|
||||||
t.Fatalf("outlierDetectionToConfig(%v) (-want, +got):\n%s", test.od, diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,21 +25,21 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/attributes"
|
"google.golang.org/grpc/attributes"
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
"google.golang.org/grpc/balancer/base"
|
"google.golang.org/grpc/balancer/base"
|
||||||
"google.golang.org/grpc/balancer/roundrobin"
|
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/internal/balancer/nop"
|
||||||
"google.golang.org/grpc/internal/buffer"
|
"google.golang.org/grpc/internal/buffer"
|
||||||
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
"google.golang.org/grpc/internal/grpcsync"
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
"google.golang.org/grpc/internal/pretty"
|
"google.golang.org/grpc/internal/pretty"
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/priority"
|
"google.golang.org/grpc/xds/internal/balancer/priority"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/ringhash"
|
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient"
|
"google.golang.org/grpc/xds/internal/xdsclient"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
|
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
|
||||||
)
|
)
|
||||||
|
@ -65,12 +65,12 @@ func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Bal
|
||||||
priorityBuilder := balancer.Get(priority.Name)
|
priorityBuilder := balancer.Get(priority.Name)
|
||||||
if priorityBuilder == nil {
|
if priorityBuilder == nil {
|
||||||
logger.Errorf("%q LB policy is needed but not registered", priority.Name)
|
logger.Errorf("%q LB policy is needed but not registered", priority.Name)
|
||||||
return nil
|
return nop.NewBalancer(cc, fmt.Errorf("%q LB policy is needed but not registered", priority.Name))
|
||||||
}
|
}
|
||||||
priorityConfigParser, ok := priorityBuilder.(balancer.ConfigParser)
|
priorityConfigParser, ok := priorityBuilder.(balancer.ConfigParser)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Errorf("%q LB policy does not implement a config parser", priority.Name)
|
logger.Errorf("%q LB policy does not implement a config parser", priority.Name)
|
||||||
return nil
|
return nop.NewBalancer(cc, fmt.Errorf("%q LB policy does not implement a config parser", priority.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
b := &clusterResolverBalancer{
|
b := &clusterResolverBalancer{
|
||||||
|
@ -99,15 +99,48 @@ func (bb) Name() string {
|
||||||
return Name
|
return Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
func (bb) ParseConfig(j json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
var cfg LBConfig
|
odBuilder := balancer.Get(outlierdetection.Name)
|
||||||
if err := json.Unmarshal(c, &cfg); err != nil {
|
if odBuilder == nil {
|
||||||
return nil, fmt.Errorf("unable to unmarshal balancer config %s into cluster-resolver config, error: %v", string(c), err)
|
// Shouldn't happen, registered through imported Outlier Detection,
|
||||||
|
// defensive programming.
|
||||||
|
return nil, fmt.Errorf("%q LB policy is needed but not registered", outlierdetection.Name)
|
||||||
}
|
}
|
||||||
if lbp := cfg.XDSLBPolicy; lbp != nil && !strings.EqualFold(lbp.Name, roundrobin.Name) && !strings.EqualFold(lbp.Name, ringhash.Name) {
|
odParser, ok := odBuilder.(balancer.ConfigParser)
|
||||||
return nil, fmt.Errorf("unsupported child policy with name %q, not one of {%q,%q}", lbp.Name, roundrobin.Name, ringhash.Name)
|
if !ok {
|
||||||
|
// Shouldn't happen, imported Outlier Detection builder has this method.
|
||||||
|
return nil, fmt.Errorf("%q LB policy does not implement a config parser", outlierdetection.Name)
|
||||||
}
|
}
|
||||||
return &cfg, nil
|
|
||||||
|
var cfg *LBConfig
|
||||||
|
if err := json.Unmarshal(j, &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to unmarshal balancer config %s into cluster-resolver config, error: %v", string(j), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if envconfig.XDSOutlierDetection {
|
||||||
|
for i, dm := range cfg.DiscoveryMechanisms {
|
||||||
|
lbCfg, err := odParser.ParseConfig(dm.OutlierDetection)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing Outlier Detection config %v: %v", dm.OutlierDetection, err)
|
||||||
|
}
|
||||||
|
odCfg, ok := lbCfg.(*outlierdetection.LBConfig)
|
||||||
|
if !ok {
|
||||||
|
// Shouldn't happen, Parser built at build time with Outlier Detection
|
||||||
|
// builder pulled from gRPC LB Registry.
|
||||||
|
return nil, fmt.Errorf("odParser returned config with unexpected type %T: %v", lbCfg, lbCfg)
|
||||||
|
}
|
||||||
|
cfg.DiscoveryMechanisms[i].outlierDetection = *odCfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(cfg.XDSLBPolicy, &cfg.xdsLBPolicy); err != nil {
|
||||||
|
// This will never occur, valid configuration is emitted from the xDS
|
||||||
|
// Client. Validity is already checked in the xDS Client, however, this
|
||||||
|
// double validation is present because Unmarshalling and Validating are
|
||||||
|
// coupled into one json.Unmarshal operation). We will switch this in
|
||||||
|
// the future to two separate operations.
|
||||||
|
return nil, fmt.Errorf("error unmarshaling xDS LB Policy: %v", err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ccUpdate wraps a clientConn update received from gRPC.
|
// ccUpdate wraps a clientConn update received from gRPC.
|
||||||
|
@ -208,7 +241,7 @@ func (b *clusterResolverBalancer) updateChildConfig() {
|
||||||
b.child = newChildBalancer(b.priorityBuilder, b.cc, b.bOpts)
|
b.child = newChildBalancer(b.priorityBuilder, b.cc, b.bOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
childCfgBytes, addrs, err := buildPriorityConfigJSON(b.priorities, b.config.XDSLBPolicy)
|
childCfgBytes, addrs, err := buildPriorityConfigJSON(b.priorities, &b.config.xdsLBPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Warningf("Failed to build child policy config: %v", err)
|
b.logger.Warningf("Failed to build child policy config: %v", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/internal/grpctest"
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/internal/testutils"
|
"google.golang.org/grpc/internal/testutils"
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
xdsinternal "google.golang.org/grpc/xds/internal"
|
xdsinternal "google.golang.org/grpc/xds/internal"
|
||||||
|
@ -325,12 +325,16 @@ func newLBConfigWithOneEDS(edsServiceName string) *LBConfig {
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: edsServiceName,
|
EDSServiceName: edsServiceName,
|
||||||
}},
|
}},
|
||||||
|
xdsLBPolicy: iserviceconfig.BalancerConfig{
|
||||||
|
Name: "ROUND_ROBIN",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLBConfigWithOneEDSAndOutlierDetection(edsServiceName string, odCfg outlierdetection.LBConfig) *LBConfig {
|
func newLBConfigWithOneEDSAndOutlierDetection(edsServiceName string, odCfg outlierdetection.LBConfig) *LBConfig {
|
||||||
lbCfg := newLBConfigWithOneEDS(edsServiceName)
|
lbCfg := newLBConfigWithOneEDS(edsServiceName)
|
||||||
lbCfg.DiscoveryMechanisms[0].OutlierDetection = odCfg
|
lbCfg.DiscoveryMechanisms[0].outlierDetection = odCfg
|
||||||
return lbCfg
|
return lbCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,15 +385,22 @@ func (s) TestOutlierDetection(t *testing.T) {
|
||||||
pCfgWant := &priority.LBConfig{
|
pCfgWant := &priority.LBConfig{
|
||||||
Children: map[string]*priority.Child{
|
Children: map[string]*priority.Child{
|
||||||
"priority-0-0": {
|
"priority-0-0": {
|
||||||
Config: &internalserviceconfig.BalancerConfig{
|
Config: &iserviceconfig.BalancerConfig{
|
||||||
Name: outlierdetection.Name,
|
Name: outlierdetection.Name,
|
||||||
Config: &outlierdetection.LBConfig{
|
Config: &outlierdetection.LBConfig{
|
||||||
Interval: 1<<63 - 1,
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
EDSServiceName: "test-eds-service-name",
|
EDSServiceName: "test-eds-service-name",
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "ROUND_ROBIN",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -102,11 +102,13 @@ type DiscoveryMechanism struct {
|
||||||
DNSHostname string `json:"dnsHostname,omitempty"`
|
DNSHostname string `json:"dnsHostname,omitempty"`
|
||||||
// OutlierDetection is the Outlier Detection LB configuration for this
|
// OutlierDetection is the Outlier Detection LB configuration for this
|
||||||
// priority.
|
// priority.
|
||||||
OutlierDetection outlierdetection.LBConfig `json:"outlierDetection,omitempty"`
|
OutlierDetection json.RawMessage `json:"outlierDetection,omitempty"`
|
||||||
|
outlierDetection outlierdetection.LBConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal returns whether the DiscoveryMechanism is the same with the parameter.
|
// Equal returns whether the DiscoveryMechanism is the same with the parameter.
|
||||||
func (dm DiscoveryMechanism) Equal(b DiscoveryMechanism) bool {
|
func (dm DiscoveryMechanism) Equal(b DiscoveryMechanism) bool {
|
||||||
|
od := &dm.outlierDetection
|
||||||
switch {
|
switch {
|
||||||
case dm.Cluster != b.Cluster:
|
case dm.Cluster != b.Cluster:
|
||||||
return false
|
return false
|
||||||
|
@ -118,7 +120,7 @@ func (dm DiscoveryMechanism) Equal(b DiscoveryMechanism) bool {
|
||||||
return false
|
return false
|
||||||
case dm.DNSHostname != b.DNSHostname:
|
case dm.DNSHostname != b.DNSHostname:
|
||||||
return false
|
return false
|
||||||
case !dm.OutlierDetection.EqualIgnoringChildPolicy(&b.OutlierDetection):
|
case !od.EqualIgnoringChildPolicy(&b.outlierDetection):
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,16 +153,6 @@ type LBConfig struct {
|
||||||
DiscoveryMechanisms []DiscoveryMechanism `json:"discoveryMechanisms,omitempty"`
|
DiscoveryMechanisms []DiscoveryMechanism `json:"discoveryMechanisms,omitempty"`
|
||||||
|
|
||||||
// XDSLBPolicy specifies the policy for locality picking and endpoint picking.
|
// XDSLBPolicy specifies the policy for locality picking and endpoint picking.
|
||||||
//
|
XDSLBPolicy json.RawMessage `json:"xdsLbPolicy,omitempty"`
|
||||||
// Note that it's not normal balancing policy, and it can only be either
|
xdsLBPolicy internalserviceconfig.BalancerConfig
|
||||||
// ROUND_ROBIN or RING_HASH.
|
|
||||||
//
|
|
||||||
// For ROUND_ROBIN, the policy name will be "ROUND_ROBIN", and the config
|
|
||||||
// will be empty. This sets the locality-picking policy to weighted_target
|
|
||||||
// and the endpoint-picking policy to round_robin.
|
|
||||||
//
|
|
||||||
// For RING_HASH, the policy name will be "RING_HASH", and the config will
|
|
||||||
// be lb config for the ring_hash_experimental LB Policy. ring_hash policy
|
|
||||||
// is responsible for both locality picking and endpoint picking.
|
|
||||||
XDSLBPolicy *internalserviceconfig.BalancerConfig `json:"xdsLbPolicy,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,13 @@ package clusterresolver
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
|
"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/ringhash"
|
"google.golang.org/grpc/xds/internal/balancer/ringhash"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
|
"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
|
||||||
)
|
)
|
||||||
|
@ -101,8 +104,10 @@ const (
|
||||||
},
|
},
|
||||||
"maxConcurrentRequests": 314,
|
"maxConcurrentRequests": 314,
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "test-eds-service-name"
|
"edsServiceName": "test-eds-service-name",
|
||||||
}]
|
"outlierDetection": {}
|
||||||
|
}],
|
||||||
|
"xdsLbPolicy":[{"ROUND_ROBIN":{}}]
|
||||||
}`
|
}`
|
||||||
testJSONConfig2 = `{
|
testJSONConfig2 = `{
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
|
@ -113,10 +118,13 @@ const (
|
||||||
},
|
},
|
||||||
"maxConcurrentRequests": 314,
|
"maxConcurrentRequests": 314,
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "test-eds-service-name"
|
"edsServiceName": "test-eds-service-name",
|
||||||
|
"outlierDetection": {}
|
||||||
},{
|
},{
|
||||||
"type": "LOGICAL_DNS"
|
"type": "LOGICAL_DNS",
|
||||||
}]
|
"outlierDetection": {}
|
||||||
|
}],
|
||||||
|
"xdsLbPolicy":[{"ROUND_ROBIN":{}}]
|
||||||
}`
|
}`
|
||||||
testJSONConfig3 = `{
|
testJSONConfig3 = `{
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
|
@ -127,7 +135,8 @@ const (
|
||||||
},
|
},
|
||||||
"maxConcurrentRequests": 314,
|
"maxConcurrentRequests": 314,
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "test-eds-service-name"
|
"edsServiceName": "test-eds-service-name",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"ROUND_ROBIN":{}}]
|
"xdsLbPolicy":[{"ROUND_ROBIN":{}}]
|
||||||
}`
|
}`
|
||||||
|
@ -140,7 +149,8 @@ const (
|
||||||
},
|
},
|
||||||
"maxConcurrentRequests": 314,
|
"maxConcurrentRequests": 314,
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "test-eds-service-name"
|
"edsServiceName": "test-eds-service-name",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"ring_hash_experimental":{}}]
|
"xdsLbPolicy":[{"ring_hash_experimental":{}}]
|
||||||
}`
|
}`
|
||||||
|
@ -153,9 +163,10 @@ const (
|
||||||
},
|
},
|
||||||
"maxConcurrentRequests": 314,
|
"maxConcurrentRequests": 314,
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "test-eds-service-name"
|
"edsServiceName": "test-eds-service-name",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"pick_first":{}}]
|
"xdsLbPolicy":[{"ROUND_ROBIN":{}}]
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -190,9 +201,19 @@ func TestParseConfig(t *testing.T) {
|
||||||
MaxConcurrentRequests: newUint32(testMaxRequests),
|
MaxConcurrentRequests: newUint32(testMaxRequests),
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: testEDSService,
|
EDSServiceName: testEDSService,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XDSLBPolicy: nil,
|
xdsLBPolicy: iserviceconfig.BalancerConfig{ // do we want to make this not pointer
|
||||||
|
Name: "ROUND_ROBIN",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
@ -207,12 +228,29 @@ func TestParseConfig(t *testing.T) {
|
||||||
MaxConcurrentRequests: newUint32(testMaxRequests),
|
MaxConcurrentRequests: newUint32(testMaxRequests),
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: testEDSService,
|
EDSServiceName: testEDSService,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: DiscoveryMechanismTypeLogicalDNS,
|
Type: DiscoveryMechanismTypeLogicalDNS,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XDSLBPolicy: nil,
|
xdsLBPolicy: iserviceconfig.BalancerConfig{
|
||||||
|
Name: "ROUND_ROBIN",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
@ -227,9 +265,16 @@ func TestParseConfig(t *testing.T) {
|
||||||
MaxConcurrentRequests: newUint32(testMaxRequests),
|
MaxConcurrentRequests: newUint32(testMaxRequests),
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: testEDSService,
|
EDSServiceName: testEDSService,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XDSLBPolicy: &internalserviceconfig.BalancerConfig{
|
xdsLBPolicy: iserviceconfig.BalancerConfig{
|
||||||
Name: "ROUND_ROBIN",
|
Name: "ROUND_ROBIN",
|
||||||
Config: nil,
|
Config: nil,
|
||||||
},
|
},
|
||||||
|
@ -247,9 +292,16 @@ func TestParseConfig(t *testing.T) {
|
||||||
MaxConcurrentRequests: newUint32(testMaxRequests),
|
MaxConcurrentRequests: newUint32(testMaxRequests),
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: testEDSService,
|
EDSServiceName: testEDSService,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XDSLBPolicy: &internalserviceconfig.BalancerConfig{
|
xdsLBPolicy: iserviceconfig.BalancerConfig{
|
||||||
Name: ringhash.Name,
|
Name: ringhash.Name,
|
||||||
Config: &ringhash.LBConfig{MinRingSize: 1024, MaxRingSize: 4096}, // Ringhash LB config with default min and max.
|
Config: &ringhash.LBConfig{MinRingSize: 1024, MaxRingSize: 4096}, // Ringhash LB config with default min and max.
|
||||||
},
|
},
|
||||||
|
@ -257,9 +309,31 @@ func TestParseConfig(t *testing.T) {
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unsupported picking policy",
|
name: "noop-outlier-detection",
|
||||||
js: testJSONConfig5,
|
js: testJSONConfig5,
|
||||||
wantErr: true,
|
want: &LBConfig{
|
||||||
|
DiscoveryMechanisms: []DiscoveryMechanism{
|
||||||
|
{
|
||||||
|
Cluster: testClusterName,
|
||||||
|
LoadReportingServer: testLRSServerConfig,
|
||||||
|
MaxConcurrentRequests: newUint32(testMaxRequests),
|
||||||
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
|
EDSServiceName: testEDSService,
|
||||||
|
outlierDetection: outlierdetection.LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
// sre and fpe are both nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xdsLBPolicy: iserviceconfig.BalancerConfig{
|
||||||
|
Name: "ROUND_ROBIN",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -279,7 +353,7 @@ func TestParseConfig(t *testing.T) {
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(got, tt.want); diff != "" {
|
if diff := cmp.Diff(got, tt.want, cmp.AllowUnexported(LBConfig{}), cmpopts.IgnoreFields(LBConfig{}, "XDSLBPolicy")); diff != "" {
|
||||||
t.Errorf("parseConfig() got unexpected output, diff (-got +want): %v", diff)
|
t.Errorf("parseConfig() got unexpected output, diff (-got +want): %v", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -100,7 +100,7 @@ func buildPriorityConfig(priorities []priorityConfig, xdsLBPolicy *internalservi
|
||||||
retAddrs = append(retAddrs, addrs...)
|
retAddrs = append(retAddrs, addrs...)
|
||||||
var odCfgs map[string]*outlierdetection.LBConfig
|
var odCfgs map[string]*outlierdetection.LBConfig
|
||||||
if envconfig.XDSOutlierDetection {
|
if envconfig.XDSOutlierDetection {
|
||||||
odCfgs = convertClusterImplMapToOutlierDetection(configs, p.mechanism.OutlierDetection)
|
odCfgs = convertClusterImplMapToOutlierDetection(configs, p.mechanism.outlierDetection)
|
||||||
for n, c := range odCfgs {
|
for n, c := range odCfgs {
|
||||||
retConfig.Children[n] = &priority.Child{
|
retConfig.Children[n] = &priority.Child{
|
||||||
Config: &internalserviceconfig.BalancerConfig{Name: outlierdetection.Name, Config: c},
|
Config: &internalserviceconfig.BalancerConfig{Name: outlierdetection.Name, Config: c},
|
||||||
|
@ -124,7 +124,7 @@ func buildPriorityConfig(priorities []priorityConfig, xdsLBPolicy *internalservi
|
||||||
retAddrs = append(retAddrs, addrs...)
|
retAddrs = append(retAddrs, addrs...)
|
||||||
var odCfg *outlierdetection.LBConfig
|
var odCfg *outlierdetection.LBConfig
|
||||||
if envconfig.XDSOutlierDetection {
|
if envconfig.XDSOutlierDetection {
|
||||||
odCfg = makeClusterImplOutlierDetectionChild(config, p.mechanism.OutlierDetection)
|
odCfg = makeClusterImplOutlierDetectionChild(config, p.mechanism.outlierDetection)
|
||||||
retConfig.Children[name] = &priority.Child{
|
retConfig.Children[name] = &priority.Child{
|
||||||
Config: &internalserviceconfig.BalancerConfig{Name: outlierdetection.Name, Config: odCfg},
|
Config: &internalserviceconfig.BalancerConfig{Name: outlierdetection.Name, Config: odCfg},
|
||||||
// Not ignore re-resolution from DNS children, they will trigger
|
// Not ignore re-resolution from DNS children, they will trigger
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"google.golang.org/grpc/attributes"
|
"google.golang.org/grpc/attributes"
|
||||||
|
@ -31,7 +32,7 @@ import (
|
||||||
"google.golang.org/grpc/balancer/roundrobin"
|
"google.golang.org/grpc/balancer/roundrobin"
|
||||||
"google.golang.org/grpc/balancer/weightedroundrobin"
|
"google.golang.org/grpc/balancer/weightedroundrobin"
|
||||||
"google.golang.org/grpc/internal/hierarchy"
|
"google.golang.org/grpc/internal/hierarchy"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
"google.golang.org/grpc/xds/internal"
|
"google.golang.org/grpc/xds/internal"
|
||||||
"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
|
"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
|
||||||
|
@ -72,7 +73,10 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
noopODCfg = outlierdetection.LBConfig{
|
noopODCfg = outlierdetection.LBConfig{
|
||||||
Interval: 1<<63 - 1,
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,7 +198,7 @@ func TestBuildPriorityConfig(t *testing.T) {
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
EDSServiceName: testEDSServiceName,
|
EDSServiceName: testEDSServiceName,
|
||||||
OutlierDetection: noopODCfg,
|
outlierDetection: noopODCfg,
|
||||||
},
|
},
|
||||||
edsResp: xdsresource.EndpointsUpdate{
|
edsResp: xdsresource.EndpointsUpdate{
|
||||||
Localities: []xdsresource.Locality{
|
Localities: []xdsresource.Locality{
|
||||||
|
@ -211,7 +215,7 @@ func TestBuildPriorityConfig(t *testing.T) {
|
||||||
mechanism: DiscoveryMechanism{
|
mechanism: DiscoveryMechanism{
|
||||||
Cluster: testClusterName2,
|
Cluster: testClusterName2,
|
||||||
Type: DiscoveryMechanismTypeLogicalDNS,
|
Type: DiscoveryMechanismTypeLogicalDNS,
|
||||||
OutlierDetection: noopODCfg,
|
outlierDetection: noopODCfg,
|
||||||
},
|
},
|
||||||
addresses: testAddressStrs[4],
|
addresses: testAddressStrs[4],
|
||||||
childNameGen: newNameGenerator(1),
|
childNameGen: newNameGenerator(1),
|
||||||
|
@ -221,11 +225,14 @@ func TestBuildPriorityConfig(t *testing.T) {
|
||||||
wantConfig := &priority.LBConfig{
|
wantConfig := &priority.LBConfig{
|
||||||
Children: map[string]*priority.Child{
|
Children: map[string]*priority.Child{
|
||||||
"priority-0-0": {
|
"priority-0-0": {
|
||||||
Config: &internalserviceconfig.BalancerConfig{
|
Config: &iserviceconfig.BalancerConfig{
|
||||||
Name: outlierdetection.Name,
|
Name: outlierdetection.Name,
|
||||||
Config: &outlierdetection.LBConfig{
|
Config: &outlierdetection.LBConfig{
|
||||||
Interval: 1<<63 - 1,
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
|
@ -238,11 +245,14 @@ func TestBuildPriorityConfig(t *testing.T) {
|
||||||
IgnoreReresolutionRequests: true,
|
IgnoreReresolutionRequests: true,
|
||||||
},
|
},
|
||||||
"priority-0-1": {
|
"priority-0-1": {
|
||||||
Config: &internalserviceconfig.BalancerConfig{
|
Config: &iserviceconfig.BalancerConfig{
|
||||||
Name: outlierdetection.Name,
|
Name: outlierdetection.Name,
|
||||||
Config: &outlierdetection.LBConfig{
|
Config: &outlierdetection.LBConfig{
|
||||||
Interval: 1<<63 - 1,
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
|
@ -255,15 +265,18 @@ func TestBuildPriorityConfig(t *testing.T) {
|
||||||
IgnoreReresolutionRequests: true,
|
IgnoreReresolutionRequests: true,
|
||||||
},
|
},
|
||||||
"priority-1": {
|
"priority-1": {
|
||||||
Config: &internalserviceconfig.BalancerConfig{
|
Config: &iserviceconfig.BalancerConfig{
|
||||||
Name: outlierdetection.Name,
|
Name: outlierdetection.Name,
|
||||||
Config: &outlierdetection.LBConfig{
|
Config: &outlierdetection.LBConfig{
|
||||||
Interval: 1<<63 - 1,
|
Interval: iserviceconfig.Duration(10 * time.Second), // default interval
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName2,
|
Cluster: testClusterName2,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{Name: "pick_first"},
|
ChildPolicy: &iserviceconfig.BalancerConfig{Name: "pick_first"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -283,7 +296,7 @@ func TestBuildClusterImplConfigForDNS(t *testing.T) {
|
||||||
wantName := "priority-3"
|
wantName := "priority-3"
|
||||||
wantConfig := &clusterimpl.LBConfig{
|
wantConfig := &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName2,
|
Cluster: testClusterName2,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "pick_first",
|
Name: "pick_first",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -500,7 +513,7 @@ func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
|
||||||
localities []xdsresource.Locality
|
localities []xdsresource.Locality
|
||||||
priorityName string
|
priorityName string
|
||||||
mechanism DiscoveryMechanism
|
mechanism DiscoveryMechanism
|
||||||
childPolicy *internalserviceconfig.BalancerConfig
|
childPolicy *iserviceconfig.BalancerConfig
|
||||||
wantConfig *clusterimpl.LBConfig
|
wantConfig *clusterimpl.LBConfig
|
||||||
wantAddrs []resolver.Address
|
wantAddrs []resolver.Address
|
||||||
wantErr bool
|
wantErr bool
|
||||||
|
@ -525,7 +538,7 @@ func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
priorityName: "test-priority",
|
priorityName: "test-priority",
|
||||||
childPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
|
childPolicy: &iserviceconfig.BalancerConfig{Name: roundrobin.Name},
|
||||||
mechanism: DiscoveryMechanism{
|
mechanism: DiscoveryMechanism{
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
|
@ -535,7 +548,7 @@ func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
|
||||||
wantConfig: &clusterimpl.LBConfig{
|
wantConfig: &clusterimpl.LBConfig{
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
EDSServiceName: testEDSService,
|
EDSServiceName: testEDSService,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
|
ChildPolicy: &iserviceconfig.BalancerConfig{Name: roundrobin.Name},
|
||||||
},
|
},
|
||||||
wantAddrs: []resolver.Address{
|
wantAddrs: []resolver.Address{
|
||||||
testAddrWithAttrs("addr-1-1", 20, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
|
testAddrWithAttrs("addr-1-1", 20, 90, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
|
||||||
|
@ -565,10 +578,10 @@ func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
priorityName: "test-priority",
|
priorityName: "test-priority",
|
||||||
childPolicy: &internalserviceconfig.BalancerConfig{Name: ringhash.Name, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}},
|
childPolicy: &iserviceconfig.BalancerConfig{Name: ringhash.Name, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}},
|
||||||
// lrsServer is nil, so LRS policy will not be used.
|
// lrsServer is nil, so LRS policy will not be used.
|
||||||
wantConfig: &clusterimpl.LBConfig{
|
wantConfig: &clusterimpl.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: ringhash.Name,
|
Name: ringhash.Name,
|
||||||
Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2},
|
Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2},
|
||||||
},
|
},
|
||||||
|
@ -638,7 +651,7 @@ func TestConvertClusterImplMapToOutlierDetection(t *testing.T) {
|
||||||
wantODCfgs: map[string]*outlierdetection.LBConfig{
|
wantODCfgs: map[string]*outlierdetection.LBConfig{
|
||||||
"child1": {
|
"child1": {
|
||||||
Interval: 1<<63 - 1,
|
Interval: 1<<63 - 1,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: "cluster1",
|
Cluster: "cluster1",
|
||||||
|
@ -663,7 +676,7 @@ func TestConvertClusterImplMapToOutlierDetection(t *testing.T) {
|
||||||
wantODCfgs: map[string]*outlierdetection.LBConfig{
|
wantODCfgs: map[string]*outlierdetection.LBConfig{
|
||||||
"child1": {
|
"child1": {
|
||||||
Interval: 1<<63 - 1,
|
Interval: 1<<63 - 1,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: "cluster1",
|
Cluster: "cluster1",
|
||||||
|
@ -672,7 +685,7 @@ func TestConvertClusterImplMapToOutlierDetection(t *testing.T) {
|
||||||
},
|
},
|
||||||
"child2": {
|
"child2": {
|
||||||
Interval: 1<<63 - 1,
|
Interval: 1<<63 - 1,
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: clusterimpl.Name,
|
Name: clusterimpl.Name,
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
Cluster: "cluster2",
|
Cluster: "cluster2",
|
||||||
|
|
|
@ -193,7 +193,8 @@ func (s) TestEDS_OneLocality(t *testing.T) {
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
"cluster": "%s",
|
"cluster": "%s",
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "%s"
|
"edsServiceName": "%s",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"round_robin":{}}]
|
"xdsLbPolicy":[{"round_robin":{}}]
|
||||||
}
|
}
|
||||||
|
@ -301,7 +302,8 @@ func (s) TestEDS_MultipleLocalities(t *testing.T) {
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
"cluster": "%s",
|
"cluster": "%s",
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "%s"
|
"edsServiceName": "%s",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"round_robin":{}}]
|
"xdsLbPolicy":[{"round_robin":{}}]
|
||||||
}
|
}
|
||||||
|
@ -422,7 +424,8 @@ func (s) TestEDS_EndpointsHealth(t *testing.T) {
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
"cluster": "%s",
|
"cluster": "%s",
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "%s"
|
"edsServiceName": "%s",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"round_robin":{}}]
|
"xdsLbPolicy":[{"round_robin":{}}]
|
||||||
}
|
}
|
||||||
|
@ -488,7 +491,8 @@ func (s) TestEDS_EmptyUpdate(t *testing.T) {
|
||||||
"discoveryMechanisms": [{
|
"discoveryMechanisms": [{
|
||||||
"cluster": "%s",
|
"cluster": "%s",
|
||||||
"type": "EDS",
|
"type": "EDS",
|
||||||
"edsServiceName": "%s"
|
"edsServiceName": "%s",
|
||||||
|
"outlierDetection": {}
|
||||||
}],
|
}],
|
||||||
"xdsLbPolicy":[{"round_robin":{}}]
|
"xdsLbPolicy":[{"round_robin":{}}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ func setupTestEDS(t *testing.T, initChild *internalserviceconfig.BalancerConfig)
|
||||||
Cluster: testClusterName,
|
Cluster: testClusterName,
|
||||||
Type: DiscoveryMechanismTypeEDS,
|
Type: DiscoveryMechanismTypeEDS,
|
||||||
}},
|
}},
|
||||||
XDSLBPolicy: wrrLocalityLBConfig,
|
xdsLBPolicy: *wrrLocalityLBConfig,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
edsb.Close()
|
edsb.Close()
|
||||||
|
@ -855,7 +855,7 @@ func (s) TestFallbackToDNS(t *testing.T) {
|
||||||
DNSHostname: testDNSTarget,
|
DNSHostname: testDNSTarget,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
XDSLBPolicy: wrrLocalityLBConfig,
|
xdsLBPolicy: *wrrLocalityLBConfig,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -23,7 +23,6 @@ package outlierdetection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -41,6 +40,7 @@ import (
|
||||||
"google.golang.org/grpc/internal/grpclog"
|
"google.golang.org/grpc/internal/grpclog"
|
||||||
"google.golang.org/grpc/internal/grpcrand"
|
"google.golang.org/grpc/internal/grpcrand"
|
||||||
"google.golang.org/grpc/internal/grpcsync"
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
)
|
)
|
||||||
|
@ -81,19 +81,27 @@ func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Ba
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
var lbCfg *LBConfig
|
lbCfg := &LBConfig{
|
||||||
if err := json.Unmarshal(s, &lbCfg); err != nil { // Validates child config if present as well.
|
// Default top layer values as documented in A50.
|
||||||
|
Interval: iserviceconfig.Duration(10 * time.Second),
|
||||||
|
BaseEjectionTime: iserviceconfig.Duration(30 * time.Second),
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(300 * time.Second),
|
||||||
|
MaxEjectionPercent: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This unmarshalling handles underlying layers sre and fpe which have their
|
||||||
|
// own defaults for their fields if either sre or fpe are present.
|
||||||
|
if err := json.Unmarshal(s, lbCfg); err != nil { // Validates child config if present as well.
|
||||||
return nil, fmt.Errorf("xds: unable to unmarshal LBconfig: %s, error: %v", string(s), err)
|
return nil, fmt.Errorf("xds: unable to unmarshal LBconfig: %s, error: %v", string(s), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: in the xds flow, these validations will never fail. The xdsclient
|
// Note: in the xds flow, these validations will never fail. The xdsclient
|
||||||
// performs the same validations as here on the xds Outlier Detection
|
// performs the same validations as here on the xds Outlier Detection
|
||||||
// resource before parsing into the internal struct which gets marshaled
|
// resource before parsing resource into JSON which this function gets
|
||||||
// into JSON before calling this function. A50 defines two separate places
|
// called with. A50 defines two separate places for these validations to
|
||||||
// for these validations to take place, the xdsclient and this ParseConfig
|
// take place, the xdsclient and this ParseConfig method. "When parsing a
|
||||||
// method. "When parsing a config from JSON, if any of these requirements is
|
// config from JSON, if any of these requirements is violated, that should
|
||||||
// violated, that should be treated as a parsing error." - A50
|
// be treated as a parsing error." - A50
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// "The google.protobuf.Duration fields interval, base_ejection_time, and
|
// "The google.protobuf.Duration fields interval, base_ejection_time, and
|
||||||
// max_ejection_time must obey the restrictions in the
|
// max_ejection_time must obey the restrictions in the
|
||||||
|
@ -122,10 +130,7 @@ func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, err
|
||||||
return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.threshold = %v; must be <= 100", lbCfg.FailurePercentageEjection.Threshold)
|
return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.threshold = %v; must be <= 100", lbCfg.FailurePercentageEjection.Threshold)
|
||||||
case lbCfg.FailurePercentageEjection != nil && lbCfg.FailurePercentageEjection.EnforcementPercentage > 100:
|
case lbCfg.FailurePercentageEjection != nil && lbCfg.FailurePercentageEjection.EnforcementPercentage > 100:
|
||||||
return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = %v; must be <= 100", lbCfg.FailurePercentageEjection.EnforcementPercentage)
|
return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = %v; must be <= 100", lbCfg.FailurePercentageEjection.EnforcementPercentage)
|
||||||
case lbCfg.ChildPolicy == nil:
|
|
||||||
return nil, errors.New("OutlierDetectionLoadBalancingConfig.child_policy must be present")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lbCfg, nil
|
return lbCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,20 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
parser := bb{}
|
parser := bb{}
|
||||||
|
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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
@ -76,7 +89,7 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "noop-lb-config",
|
name: "no-fields-set-should-get-default",
|
||||||
input: `{
|
input: `{
|
||||||
"childPolicy": [
|
"childPolicy": [
|
||||||
{
|
{
|
||||||
|
@ -87,6 +100,38 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
]
|
]
|
||||||
}`,
|
}`,
|
||||||
wantCfg: &LBConfig{
|
wantCfg: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "some-top-level-fields-set",
|
||||||
|
input: `{
|
||||||
|
"interval": "15s",
|
||||||
|
"maxEjectionTime": "350s",
|
||||||
|
"childPolicy": [
|
||||||
|
{
|
||||||
|
"xds_cluster_impl_experimental": {
|
||||||
|
"cluster": "test_cluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
// Should get set fields + defaults for unset fields.
|
||||||
|
wantCfg: &LBConfig{
|
||||||
|
Interval: iserviceconfig.Duration(15 * time.Second),
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: iserviceconfig.Duration(350 * time.Second),
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "xds_cluster_impl_experimental",
|
Name: "xds_cluster_impl_experimental",
|
||||||
Config: &clusterimpl.LBConfig{
|
Config: &clusterimpl.LBConfig{
|
||||||
|
@ -96,7 +141,253 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "good-lb-config",
|
name: "success-rate-ejection-present-but-no-fields",
|
||||||
|
input: `{
|
||||||
|
"successRateEjection": {},
|
||||||
|
"childPolicy": [
|
||||||
|
{
|
||||||
|
"xds_cluster_impl_experimental": {
|
||||||
|
"cluster": "test_cluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
// Should get defaults of success-rate-ejection struct.
|
||||||
|
wantCfg: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
SuccessRateEjection: &SuccessRateEjection{
|
||||||
|
StdevFactor: defaultSuccessRateStdevFactor,
|
||||||
|
EnforcementPercentage: defaultEnforcingSuccessRate,
|
||||||
|
MinimumHosts: defaultSuccessRateMinimumHosts,
|
||||||
|
RequestVolume: defaultSuccessRateRequestVolume,
|
||||||
|
},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success-rate-ejection-present-partially-set",
|
||||||
|
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: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
SuccessRateEjection: &SuccessRateEjection{
|
||||||
|
StdevFactor: 1000,
|
||||||
|
EnforcementPercentage: defaultEnforcingSuccessRate,
|
||||||
|
MinimumHosts: 5,
|
||||||
|
RequestVolume: defaultSuccessRateRequestVolume,
|
||||||
|
},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success-rate-ejection-present-fully-set",
|
||||||
|
input: `{
|
||||||
|
"successRateEjection": {
|
||||||
|
"stdevFactor": 1000,
|
||||||
|
"enforcementPercentage": 50,
|
||||||
|
"minimumHosts": 5,
|
||||||
|
"requestVolume": 50
|
||||||
|
},
|
||||||
|
"childPolicy": [
|
||||||
|
{
|
||||||
|
"xds_cluster_impl_experimental": {
|
||||||
|
"cluster": "test_cluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
wantCfg: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
SuccessRateEjection: &SuccessRateEjection{
|
||||||
|
StdevFactor: 1000,
|
||||||
|
EnforcementPercentage: 50,
|
||||||
|
MinimumHosts: 5,
|
||||||
|
RequestVolume: 50,
|
||||||
|
},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failure-percentage-ejection-present-but-no-fields",
|
||||||
|
input: `{
|
||||||
|
"failurePercentageEjection": {},
|
||||||
|
"childPolicy": [
|
||||||
|
{
|
||||||
|
"xds_cluster_impl_experimental": {
|
||||||
|
"cluster": "test_cluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
// Should get defaults of failure percentage ejection layer.
|
||||||
|
wantCfg: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
FailurePercentageEjection: &FailurePercentageEjection{
|
||||||
|
Threshold: defaultFailurePercentageThreshold,
|
||||||
|
EnforcementPercentage: defaultEnforcingFailurePercentage,
|
||||||
|
MinimumHosts: defaultFailurePercentageMinimumHosts,
|
||||||
|
RequestVolume: defaultFailurePercentageRequestVolume,
|
||||||
|
},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failure-percentage-ejection-present-partially-set",
|
||||||
|
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: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
FailurePercentageEjection: &FailurePercentageEjection{
|
||||||
|
Threshold: 80,
|
||||||
|
EnforcementPercentage: defaultEnforcingFailurePercentage,
|
||||||
|
MinimumHosts: 10,
|
||||||
|
RequestVolume: defaultFailurePercentageRequestVolume,
|
||||||
|
},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failure-percentage-ejection-present-fully-set",
|
||||||
|
input: `{
|
||||||
|
"failurePercentageEjection": {
|
||||||
|
"threshold": 80,
|
||||||
|
"enforcementPercentage": 100,
|
||||||
|
"minimumHosts": 10,
|
||||||
|
"requestVolume": 40
|
||||||
|
},
|
||||||
|
"childPolicy": [
|
||||||
|
{
|
||||||
|
"xds_cluster_impl_experimental": {
|
||||||
|
"cluster": "test_cluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
wantCfg: &LBConfig{
|
||||||
|
Interval: defaultInterval,
|
||||||
|
BaseEjectionTime: defaultBaseEjectionTime,
|
||||||
|
MaxEjectionTime: defaultMaxEjectionTime,
|
||||||
|
MaxEjectionPercent: defaultMaxEjectionPercent,
|
||||||
|
FailurePercentageEjection: &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: "lb-config-every-field-set-zero-value",
|
||||||
|
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: &LBConfig{
|
||||||
|
SuccessRateEjection: &SuccessRateEjection{},
|
||||||
|
FailurePercentageEjection: &FailurePercentageEjection{},
|
||||||
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
|
Name: "xds_cluster_impl_experimental",
|
||||||
|
Config: &clusterimpl.LBConfig{
|
||||||
|
Cluster: "test_cluster",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lb-config-every-field-set",
|
||||||
input: `{
|
input: `{
|
||||||
"interval": "10s",
|
"interval": "10s",
|
||||||
"baseEjectionTime": "30s",
|
"baseEjectionTime": "30s",
|
||||||
|
@ -194,28 +485,6 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
wantErr: "OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = 150; must be <= 100",
|
wantErr: "OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = 150; must be <= 100",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "child-policy-not-present",
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
wantErr: "OutlierDetectionLoadBalancingConfig.child_policy must be present",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "child-policy-present-but-parse-error",
|
name: "child-policy-present-but-parse-error",
|
||||||
input: `{
|
input: `{
|
||||||
|
@ -242,26 +511,6 @@ func (s) TestParseConfig(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
wantErr: "invalid loadBalancingConfig: no supported policies found",
|
wantErr: "invalid loadBalancingConfig: no supported policies found",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "child-policy",
|
|
||||||
input: `{
|
|
||||||
"childPolicy": [
|
|
||||||
{
|
|
||||||
"xds_cluster_impl_experimental": {
|
|
||||||
"cluster": "test_cluster"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
wantCfg: &LBConfig{
|
|
||||||
ChildPolicy: &iserviceconfig.BalancerConfig{
|
|
||||||
Name: "xds_cluster_impl_experimental",
|
|
||||||
Config: &clusterimpl.LBConfig{
|
|
||||||
Cluster: "test_cluster",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
package outlierdetection
|
package outlierdetection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
)
|
)
|
||||||
|
@ -52,6 +55,24 @@ type SuccessRateEjection struct {
|
||||||
RequestVolume uint32 `json:"requestVolume,omitempty"`
|
RequestVolume uint32 `json:"requestVolume,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For UnmarshalJSON to work correctly and set defaults without infinite
|
||||||
|
// recursion.
|
||||||
|
type successRateEjection SuccessRateEjection
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals JSON into SuccessRateEjection. If a
|
||||||
|
// SuccessRateEjection field is not set, that field will get its default value.
|
||||||
|
func (sre *SuccessRateEjection) UnmarshalJSON(j []byte) error {
|
||||||
|
sre.StdevFactor = 1900
|
||||||
|
sre.EnforcementPercentage = 100
|
||||||
|
sre.MinimumHosts = 5
|
||||||
|
sre.RequestVolume = 100
|
||||||
|
// Unmarshal JSON on a type with zero values for methods, including
|
||||||
|
// UnmarshalJSON. Overwrites defaults, leaves alone if not. typecast to
|
||||||
|
// avoid infinite recursion by not recalling this function and causing stack
|
||||||
|
// overflow.
|
||||||
|
return json.Unmarshal(j, (*successRateEjection)(sre))
|
||||||
|
}
|
||||||
|
|
||||||
// Equal returns whether the SuccessRateEjection is the same with the parameter.
|
// Equal returns whether the SuccessRateEjection is the same with the parameter.
|
||||||
func (sre *SuccessRateEjection) Equal(sre2 *SuccessRateEjection) bool {
|
func (sre *SuccessRateEjection) Equal(sre2 *SuccessRateEjection) bool {
|
||||||
if sre == nil && sre2 == nil {
|
if sre == nil && sre2 == nil {
|
||||||
|
@ -99,6 +120,25 @@ type FailurePercentageEjection struct {
|
||||||
RequestVolume uint32 `json:"requestVolume,omitempty"`
|
RequestVolume uint32 `json:"requestVolume,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For UnmarshalJSON to work correctly and set defaults without infinite
|
||||||
|
// recursion.
|
||||||
|
type failurePercentageEjection FailurePercentageEjection
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals JSON into FailurePercentageEjection. If a
|
||||||
|
// FailurePercentageEjection field is not set, that field will get its default
|
||||||
|
// value.
|
||||||
|
func (fpe *FailurePercentageEjection) UnmarshalJSON(j []byte) error {
|
||||||
|
fpe.Threshold = 85
|
||||||
|
fpe.EnforcementPercentage = 0
|
||||||
|
fpe.MinimumHosts = 5
|
||||||
|
fpe.RequestVolume = 50
|
||||||
|
// Unmarshal JSON on a type with zero values for methods, including
|
||||||
|
// UnmarshalJSON. Overwrites defaults, leaves alone if not. typecast to
|
||||||
|
// avoid infinite recursion by not recalling this function and causing stack
|
||||||
|
// overflow.
|
||||||
|
return json.Unmarshal(j, (*failurePercentageEjection)(fpe))
|
||||||
|
}
|
||||||
|
|
||||||
// Equal returns whether the FailurePercentageEjection is the same with the
|
// Equal returns whether the FailurePercentageEjection is the same with the
|
||||||
// parameter.
|
// parameter.
|
||||||
func (fpe *FailurePercentageEjection) Equal(fpe2 *FailurePercentageEjection) bool {
|
func (fpe *FailurePercentageEjection) Equal(fpe2 *FailurePercentageEjection) bool {
|
||||||
|
@ -149,6 +189,28 @@ type LBConfig struct {
|
||||||
ChildPolicy *iserviceconfig.BalancerConfig `json:"childPolicy,omitempty"`
|
ChildPolicy *iserviceconfig.BalancerConfig `json:"childPolicy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For UnmarshalJSON to work correctly and set defaults without infinite
|
||||||
|
// recursion.
|
||||||
|
type lbConfig LBConfig
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals JSON into LBConfig. If a top level LBConfig field
|
||||||
|
// (i.e. not next layer sre or fpe) is not set, that field will get its default
|
||||||
|
// value. If sre or fpe is not set, it will stay unset, otherwise it will
|
||||||
|
// unmarshal on those types populating with default values for their fields if
|
||||||
|
// needed.
|
||||||
|
func (lbc *LBConfig) UnmarshalJSON(j []byte) error {
|
||||||
|
// Default top layer values as documented in A50.
|
||||||
|
lbc.Interval = iserviceconfig.Duration(10 * time.Second)
|
||||||
|
lbc.BaseEjectionTime = iserviceconfig.Duration(30 * time.Second)
|
||||||
|
lbc.MaxEjectionTime = iserviceconfig.Duration(300 * time.Second)
|
||||||
|
lbc.MaxEjectionPercent = 10
|
||||||
|
// Unmarshal JSON on a type with zero values for methods, including
|
||||||
|
// UnmarshalJSON. Overwrites defaults, leaves alone if not. typecast to
|
||||||
|
// avoid infinite recursion by not recalling this function and causing stack
|
||||||
|
// overflow.
|
||||||
|
return json.Unmarshal(j, (*lbConfig)(lbc))
|
||||||
|
}
|
||||||
|
|
||||||
// EqualIgnoringChildPolicy returns whether the LBConfig is same with the
|
// EqualIgnoringChildPolicy returns whether the LBConfig is same with the
|
||||||
// parameter outside of the child policy, only comparing the Outlier Detection
|
// parameter outside of the child policy, only comparing the Outlier Detection
|
||||||
// specific configuration.
|
// specific configuration.
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"google.golang.org/grpc/internal/balancer/stub"
|
"google.golang.org/grpc/internal/balancer/stub"
|
||||||
"google.golang.org/grpc/internal/envconfig"
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
"google.golang.org/grpc/internal/grpctest"
|
"google.golang.org/grpc/internal/grpctest"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/internal/testutils"
|
"google.golang.org/grpc/internal/testutils"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
_ "google.golang.org/grpc/xds" // Register the xDS LB Registry Converters.
|
_ "google.golang.org/grpc/xds" // Register the xDS LB Registry Converters.
|
||||||
|
@ -107,7 +107,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
cluster *v3clusterpb.Cluster
|
cluster *v3clusterpb.Cluster
|
||||||
wantUpdate xdsresource.ClusterUpdate
|
wantUpdate xdsresource.ClusterUpdate
|
||||||
wantLBConfig *internalserviceconfig.BalancerConfig
|
wantLBConfig *iserviceconfig.BalancerConfig
|
||||||
customLBDisabled bool
|
customLBDisabled bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -142,10 +142,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
ClusterType: xdsresource.ClusterTypeLogicalDNS,
|
ClusterType: xdsresource.ClusterTypeLogicalDNS,
|
||||||
DNSHostName: "dns_host:8080",
|
DNSHostName: "dns_host:8080",
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -169,10 +169,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
ClusterName: clusterName, LRSServerConfig: xdsresource.ClusterLRSOff, ClusterType: xdsresource.ClusterTypeAggregate,
|
ClusterName: clusterName, LRSServerConfig: xdsresource.ClusterLRSOff, ClusterType: xdsresource.ClusterTypeAggregate,
|
||||||
PrioritizedClusterNames: []string{"a", "b", "c"},
|
PrioritizedClusterNames: []string{"a", "b", "c"},
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -193,10 +193,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
},
|
},
|
||||||
wantUpdate: emptyUpdate,
|
wantUpdate: emptyUpdate,
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -218,10 +218,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
|
||||||
},
|
},
|
||||||
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSOff},
|
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSOff},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -248,10 +248,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf},
|
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -290,10 +290,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf, MaxRequests: func() *uint32 { i := uint32(512); return &i }()},
|
wantUpdate: xdsresource.ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf, MaxRequests: func() *uint32 { i := uint32(512); return &i }()},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -322,7 +322,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: "ring_hash_experimental",
|
Name: "ring_hash_experimental",
|
||||||
Config: &ringhash.LBConfig{
|
Config: &ringhash.LBConfig{
|
||||||
MinRingSize: 1024,
|
MinRingSize: 1024,
|
||||||
|
@ -359,7 +359,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
ClusterName: clusterName, EDSServiceName: serviceName, LRSServerConfig: xdsresource.ClusterLRSServerSelf,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: "ring_hash_experimental",
|
Name: "ring_hash_experimental",
|
||||||
Config: &ringhash.LBConfig{
|
Config: &ringhash.LBConfig{
|
||||||
MinRingSize: 10,
|
MinRingSize: 10,
|
||||||
|
@ -397,7 +397,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName,
|
ClusterName: clusterName, EDSServiceName: serviceName,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: "ring_hash_experimental",
|
Name: "ring_hash_experimental",
|
||||||
Config: &ringhash.LBConfig{
|
Config: &ringhash.LBConfig{
|
||||||
MinRingSize: 10,
|
MinRingSize: 10,
|
||||||
|
@ -431,10 +431,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName,
|
ClusterName: clusterName, EDSServiceName: serviceName,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "round_robin",
|
Name: "round_robin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -469,10 +469,10 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName,
|
ClusterName: clusterName, EDSServiceName: serviceName,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: wrrlocality.Name,
|
Name: wrrlocality.Name,
|
||||||
Config: &wrrlocality.LBConfig{
|
Config: &wrrlocality.LBConfig{
|
||||||
ChildPolicy: &internalserviceconfig.BalancerConfig{
|
ChildPolicy: &iserviceconfig.BalancerConfig{
|
||||||
Name: "myorg.MyCustomLeastRequestPolicy",
|
Name: "myorg.MyCustomLeastRequestPolicy",
|
||||||
Config: customLBConfig{},
|
Config: customLBConfig{},
|
||||||
},
|
},
|
||||||
|
@ -516,7 +516,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName,
|
ClusterName: clusterName, EDSServiceName: serviceName,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: "ring_hash_experimental",
|
Name: "ring_hash_experimental",
|
||||||
Config: &ringhash.LBConfig{
|
Config: &ringhash.LBConfig{
|
||||||
MinRingSize: 20,
|
MinRingSize: 20,
|
||||||
|
@ -562,7 +562,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
wantUpdate: xdsresource.ClusterUpdate{
|
wantUpdate: xdsresource.ClusterUpdate{
|
||||||
ClusterName: clusterName, EDSServiceName: serviceName,
|
ClusterName: clusterName, EDSServiceName: serviceName,
|
||||||
},
|
},
|
||||||
wantLBConfig: &internalserviceconfig.BalancerConfig{
|
wantLBConfig: &iserviceconfig.BalancerConfig{
|
||||||
Name: "ring_hash_experimental",
|
Name: "ring_hash_experimental",
|
||||||
Config: &ringhash.LBConfig{
|
Config: &ringhash.LBConfig{
|
||||||
MinRingSize: 10,
|
MinRingSize: 10,
|
||||||
|
@ -592,7 +592,7 @@ func (s) TestValidateCluster_Success(t *testing.T) {
|
||||||
if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "LBPolicy")); diff != "" {
|
if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "LBPolicy")); diff != "" {
|
||||||
t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff)
|
t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff)
|
||||||
}
|
}
|
||||||
bc := &internalserviceconfig.BalancerConfig{}
|
bc := &iserviceconfig.BalancerConfig{}
|
||||||
if err := json.Unmarshal(update.LBPolicy, bc); err != nil {
|
if err := json.Unmarshal(update.LBPolicy, bc); err != nil {
|
||||||
t.Fatalf("failed to unmarshal JSON: %v", err)
|
t.Fatalf("failed to unmarshal JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package xdsresource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/anypb"
|
"google.golang.org/protobuf/types/known/anypb"
|
||||||
)
|
)
|
||||||
|
@ -52,71 +51,6 @@ const (
|
||||||
ClusterLRSServerSelf
|
ClusterLRSServerSelf
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutlierDetection is the outlier detection configuration for a cluster.
|
|
||||||
type OutlierDetection struct {
|
|
||||||
// Interval is the time interval between ejection analysis sweeps. This can
|
|
||||||
// result in both new ejections as well as addresses being returned to
|
|
||||||
// service. Defaults to 10s.
|
|
||||||
Interval time.Duration
|
|
||||||
// BaseEjectionTime is the base time that a host is ejected for. The real
|
|
||||||
// time is equal to the base time multiplied by the number of times the host
|
|
||||||
// has been ejected and is capped by MaxEjectionTime. Defaults to 30s.
|
|
||||||
BaseEjectionTime time.Duration
|
|
||||||
// MaxEjectionTime is the maximum time that an address is ejected for. If
|
|
||||||
// not specified, the default value (300s) or the BaseEjectionTime value is
|
|
||||||
// applied, whichever is larger.
|
|
||||||
MaxEjectionTime time.Duration
|
|
||||||
// MaxEjectionPercent is the maximum % of an upstream cluster that can be
|
|
||||||
// ejected due to outlier detection. Defaults to 10% but will eject at least
|
|
||||||
// one host regardless of the value.
|
|
||||||
MaxEjectionPercent uint32
|
|
||||||
// SuccessRateStdevFactor is used to determine the ejection threshold for
|
|
||||||
// success rate outlier ejection. The ejection threshold is the difference
|
|
||||||
// between the mean success rate, and the product of this factor and the
|
|
||||||
// standard deviation of the mean success rate: mean - (stdev *
|
|
||||||
// success_rate_stdev_factor). This factor is divided by a thousand to get a
|
|
||||||
// double. That is, if the desired factor is 1.9, the runtime value should
|
|
||||||
// be 1900. Defaults to 1900.
|
|
||||||
SuccessRateStdevFactor uint32
|
|
||||||
// EnforcingSuccessRate is the % chance that a host will be actually ejected
|
|
||||||
// when an outlier status is detected through success rate statistics. This
|
|
||||||
// setting can be used to disable ejection or to ramp it up slowly. Defaults
|
|
||||||
// to 100.
|
|
||||||
EnforcingSuccessRate uint32
|
|
||||||
// SuccessRateMinimumHosts is the number of hosts in a cluster that must
|
|
||||||
// have enough request volume to detect success rate outliers. If the number
|
|
||||||
// of hosts is less than this setting, outlier detection via success rate
|
|
||||||
// statistics is not performed for any host in the cluster. Defaults to 5.
|
|
||||||
SuccessRateMinimumHosts uint32
|
|
||||||
// SuccessRateRequestVolume is the minimum number of total requests that
|
|
||||||
// must be collected in one interval (as defined by the interval duration
|
|
||||||
// above) to include this host in success rate based outlier detection. If
|
|
||||||
// the volume is lower than this setting, outlier detection via success rate
|
|
||||||
// statistics is not performed for that host. Defaults to 100.
|
|
||||||
SuccessRateRequestVolume uint32
|
|
||||||
// FailurePercentageThreshold is the failure percentage to use when
|
|
||||||
// determining failure percentage-based outlier detection. If the failure
|
|
||||||
// percentage of a given host is greater than or equal to this value, it
|
|
||||||
// will be ejected. Defaults to 85.
|
|
||||||
FailurePercentageThreshold uint32
|
|
||||||
// EnforcingFailurePercentage is the % chance that a host will be actually
|
|
||||||
// ejected when an outlier status is detected through failure percentage
|
|
||||||
// statistics. This setting can be used to disable ejection or to ramp it up
|
|
||||||
// slowly. Defaults to 0.
|
|
||||||
EnforcingFailurePercentage uint32
|
|
||||||
// FailurePercentageMinimumHosts is the minimum number of hosts in a cluster
|
|
||||||
// in order to perform failure percentage-based ejection. If the total
|
|
||||||
// number of hosts in the cluster is less than this value, failure
|
|
||||||
// percentage-based ejection will not be performed. Defaults to 5.
|
|
||||||
FailurePercentageMinimumHosts uint32
|
|
||||||
// FailurePercentageRequestVolume is the minimum number of total requests
|
|
||||||
// that must be collected in one interval (as defined by the interval
|
|
||||||
// duration above) to perform failure percentage-based ejection for this
|
|
||||||
// host. If the volume is lower than this setting, failure percentage-based
|
|
||||||
// ejection will not be performed for this host. Defaults to 50.
|
|
||||||
FailurePercentageRequestVolume uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClusterUpdate contains information from a received CDS response, which is of
|
// ClusterUpdate contains information from a received CDS response, which is of
|
||||||
// interest to the registered CDS watcher.
|
// interest to the registered CDS watcher.
|
||||||
type ClusterUpdate struct {
|
type ClusterUpdate struct {
|
||||||
|
@ -147,7 +81,7 @@ type ClusterUpdate struct {
|
||||||
|
|
||||||
// OutlierDetection is the outlier detection configuration for this cluster.
|
// OutlierDetection is the outlier detection configuration for this cluster.
|
||||||
// If nil, it means this cluster does not use the outlier detection feature.
|
// If nil, it means this cluster does not use the outlier detection feature.
|
||||||
OutlierDetection *OutlierDetection
|
OutlierDetection json.RawMessage
|
||||||
|
|
||||||
// Raw is the resource from the xds response.
|
// Raw is the resource from the xds response.
|
||||||
Raw *anypb.Any
|
Raw *anypb.Any
|
||||||
|
|
|
@ -33,7 +33,7 @@ import (
|
||||||
|
|
||||||
"google.golang.org/grpc/internal/envconfig"
|
"google.golang.org/grpc/internal/envconfig"
|
||||||
"google.golang.org/grpc/internal/pretty"
|
"google.golang.org/grpc/internal/pretty"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/internal/xds/matcher"
|
"google.golang.org/grpc/internal/xds/matcher"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
|
"google.golang.org/grpc/xds/internal/xdsclient/xdslbregistry"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
|
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
|
||||||
|
@ -118,7 +118,7 @@ func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (Clu
|
||||||
|
|
||||||
// Process outlier detection received from the control plane iff the
|
// Process outlier detection received from the control plane iff the
|
||||||
// corresponding environment variable is set.
|
// corresponding environment variable is set.
|
||||||
var od *OutlierDetection
|
var od json.RawMessage
|
||||||
if envconfig.XDSOutlierDetection {
|
if envconfig.XDSOutlierDetection {
|
||||||
var err error
|
var err error
|
||||||
if od, err = outlierConfigFromCluster(cluster); err != nil {
|
if od, err = outlierConfigFromCluster(cluster); err != nil {
|
||||||
|
@ -134,7 +134,7 @@ func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (Clu
|
||||||
// "It will be the responsibility of the XdsClient to validate the
|
// "It will be the responsibility of the XdsClient to validate the
|
||||||
// converted configuration. It will do this by having the gRPC LB policy
|
// converted configuration. It will do this by having the gRPC LB policy
|
||||||
// registry parse the configuration." - A52
|
// registry parse the configuration." - A52
|
||||||
bc := &internalserviceconfig.BalancerConfig{}
|
bc := &iserviceconfig.BalancerConfig{}
|
||||||
if err := json.Unmarshal(lbPolicy, bc); err != nil {
|
if err := json.Unmarshal(lbPolicy, bc); err != nil {
|
||||||
return ClusterUpdate{}, fmt.Errorf("JSON generated from xDS LB policy registry: %s is invalid: %v", pretty.FormatJSON(lbPolicy), err)
|
return ClusterUpdate{}, fmt.Errorf("JSON generated from xDS LB policy registry: %s is invalid: %v", pretty.FormatJSON(lbPolicy), err)
|
||||||
}
|
}
|
||||||
|
@ -490,59 +490,87 @@ func circuitBreakersFromCluster(cluster *v3clusterpb.Cluster) *uint32 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// outlierConfigFromCluster extracts the relevant outlier detection
|
// idurationp takes a time.Duration and converts it to an internal duration, and
|
||||||
// configuration from the received cluster resource. Returns nil if no
|
// returns a pointer to that internal duration.
|
||||||
// OutlierDetection field set in the cluster resource.
|
func idurationp(d time.Duration) *iserviceconfig.Duration {
|
||||||
func outlierConfigFromCluster(cluster *v3clusterpb.Cluster) (*OutlierDetection, error) {
|
id := iserviceconfig.Duration(d)
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint32p(i uint32) *uint32 {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper types to prepare Outlier Detection JSON. Pointer types to distinguish
|
||||||
|
// between unset and a zero value.
|
||||||
|
type successRateEjection struct {
|
||||||
|
StdevFactor *uint32 `json:"stdevFactor,omitempty"`
|
||||||
|
EnforcementPercentage *uint32 `json:"enforcementPercentage,omitempty"`
|
||||||
|
MinimumHosts *uint32 `json:"minimumHosts,omitempty"`
|
||||||
|
RequestVolume *uint32 `json:"requestVolume,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type failurePercentageEjection struct {
|
||||||
|
Threshold *uint32 `json:"threshold,omitempty"`
|
||||||
|
EnforcementPercentage *uint32 `json:"enforcementPercentage,omitempty"`
|
||||||
|
MinimumHosts *uint32 `json:"minimumHosts,omitempty"`
|
||||||
|
RequestVolume *uint32 `json:"requestVolume,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type odLBConfig struct {
|
||||||
|
Interval *iserviceconfig.Duration `json:"interval,omitempty"`
|
||||||
|
BaseEjectionTime *iserviceconfig.Duration `json:"baseEjectionTime,omitempty"`
|
||||||
|
MaxEjectionTime *iserviceconfig.Duration `json:"maxEjectionTime,omitempty"`
|
||||||
|
MaxEjectionPercent *uint32 `json:"maxEjectionPercent,omitempty"`
|
||||||
|
SuccessRateEjection *successRateEjection `json:"successRateEjection,omitempty"`
|
||||||
|
FailurePercentageEjection *failurePercentageEjection `json:"failurePercentageEjection,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// outlierConfigFromCluster converts the received Outlier Detection
|
||||||
|
// configuration into JSON configuration for Outlier Detection, taking into
|
||||||
|
// account xDS Defaults. Returns nil if no OutlierDetection field set in the
|
||||||
|
// cluster resource.
|
||||||
|
func outlierConfigFromCluster(cluster *v3clusterpb.Cluster) (json.RawMessage, error) {
|
||||||
od := cluster.GetOutlierDetection()
|
od := cluster.GetOutlierDetection()
|
||||||
if od == nil {
|
if od == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
const (
|
|
||||||
defaultInterval = 10 * time.Second
|
// "The outlier_detection field of the Cluster resource should have its fields
|
||||||
defaultBaseEjectionTime = 30 * time.Second
|
// validated according to the rules for the corresponding LB policy config
|
||||||
defaultMaxEjectionTime = 300 * time.Second
|
// fields in the above "Validation" section. If any of these requirements is
|
||||||
defaultMaxEjectionPercent = 10
|
// violated, the Cluster resource should be NACKed." - A50
|
||||||
defaultSuccessRateStdevFactor = 1900
|
|
||||||
defaultEnforcingSuccessRate = 100
|
|
||||||
defaultSuccessRateMinimumHosts = 5
|
|
||||||
defaultSuccessRateRequestVolume = 100
|
|
||||||
defaultFailurePercentageThreshold = 85
|
|
||||||
defaultEnforcingFailurePercentage = 0
|
|
||||||
defaultFailurePercentageMinimumHosts = 5
|
|
||||||
defaultFailurePercentageRequestVolume = 50
|
|
||||||
)
|
|
||||||
// "The google.protobuf.Duration fields interval, base_ejection_time, and
|
// "The google.protobuf.Duration fields interval, base_ejection_time, and
|
||||||
// max_ejection_time must obey the restrictions in the
|
// max_ejection_time must obey the restrictions in the
|
||||||
// google.protobuf.Duration documentation and they must have non-negative
|
// google.protobuf.Duration documentation and they must have non-negative
|
||||||
// values." - A50
|
// values." - A50
|
||||||
interval := defaultInterval
|
var interval *iserviceconfig.Duration
|
||||||
if i := od.GetInterval(); i != nil {
|
if i := od.GetInterval(); i != nil {
|
||||||
if err := i.CheckValid(); err != nil {
|
if err := i.CheckValid(); err != nil {
|
||||||
return nil, fmt.Errorf("outlier_detection.interval is invalid with error: %v", err)
|
return nil, fmt.Errorf("outlier_detection.interval is invalid with error: %v", err)
|
||||||
}
|
}
|
||||||
if interval = i.AsDuration(); interval < 0 {
|
if interval = idurationp(i.AsDuration()); *interval < 0 {
|
||||||
return nil, fmt.Errorf("outlier_detection.interval = %v; must be a valid duration and >= 0", interval)
|
return nil, fmt.Errorf("outlier_detection.interval = %v; must be a valid duration and >= 0", *interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseEjectionTime := defaultBaseEjectionTime
|
var baseEjectionTime *iserviceconfig.Duration
|
||||||
if bet := od.GetBaseEjectionTime(); bet != nil {
|
if bet := od.GetBaseEjectionTime(); bet != nil {
|
||||||
if err := bet.CheckValid(); err != nil {
|
if err := bet.CheckValid(); err != nil {
|
||||||
return nil, fmt.Errorf("outlier_detection.base_ejection_time is invalid with error: %v", err)
|
return nil, fmt.Errorf("outlier_detection.base_ejection_time is invalid with error: %v", err)
|
||||||
}
|
}
|
||||||
if baseEjectionTime = bet.AsDuration(); baseEjectionTime < 0 {
|
if baseEjectionTime = idurationp(bet.AsDuration()); *baseEjectionTime < 0 {
|
||||||
return nil, fmt.Errorf("outlier_detection.base_ejection_time = %v; must be >= 0", baseEjectionTime)
|
return nil, fmt.Errorf("outlier_detection.base_ejection_time = %v; must be >= 0", *baseEjectionTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxEjectionTime := defaultMaxEjectionTime
|
var maxEjectionTime *iserviceconfig.Duration
|
||||||
if met := od.GetMaxEjectionTime(); met != nil {
|
if met := od.GetMaxEjectionTime(); met != nil {
|
||||||
if err := met.CheckValid(); err != nil {
|
if err := met.CheckValid(); err != nil {
|
||||||
return nil, fmt.Errorf("outlier_detection.max_ejection_time is invalid: %v", err)
|
return nil, fmt.Errorf("outlier_detection.max_ejection_time is invalid: %v", err)
|
||||||
}
|
}
|
||||||
if maxEjectionTime = met.AsDuration(); maxEjectionTime < 0 {
|
if maxEjectionTime = idurationp(met.AsDuration()); *maxEjectionTime < 0 {
|
||||||
return nil, fmt.Errorf("outlier_detection.max_ejection_time = %v; must be >= 0", maxEjectionTime)
|
return nil, fmt.Errorf("outlier_detection.max_ejection_time = %v; must be >= 0", *maxEjectionTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,64 +578,91 @@ func outlierConfigFromCluster(cluster *v3clusterpb.Cluster) (*OutlierDetection,
|
||||||
// failure_percentage_threshold, and enforcing_failure_percentage must have
|
// failure_percentage_threshold, and enforcing_failure_percentage must have
|
||||||
// values less than or equal to 100. If any of these requirements is
|
// values less than or equal to 100. If any of these requirements is
|
||||||
// violated, the Cluster resource should be NACKed." - A50
|
// violated, the Cluster resource should be NACKed." - A50
|
||||||
maxEjectionPercent := uint32(defaultMaxEjectionPercent)
|
var maxEjectionPercent *uint32
|
||||||
if mep := od.GetMaxEjectionPercent(); mep != nil {
|
if mep := od.GetMaxEjectionPercent(); mep != nil {
|
||||||
if maxEjectionPercent = mep.GetValue(); maxEjectionPercent > 100 {
|
if maxEjectionPercent = uint32p(mep.GetValue()); *maxEjectionPercent > 100 {
|
||||||
return nil, fmt.Errorf("outlier_detection.max_ejection_percent = %v; must be <= 100", maxEjectionPercent)
|
return nil, fmt.Errorf("outlier_detection.max_ejection_percent = %v; must be <= 100", *maxEjectionPercent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enforcingSuccessRate := uint32(defaultEnforcingSuccessRate)
|
// "if the enforcing_success_rate field is set to 0, the config
|
||||||
|
// success_rate_ejection field will be null and all success_rate_* fields
|
||||||
|
// will be ignored." - A50
|
||||||
|
var enforcingSuccessRate *uint32
|
||||||
if esr := od.GetEnforcingSuccessRate(); esr != nil {
|
if esr := od.GetEnforcingSuccessRate(); esr != nil {
|
||||||
if enforcingSuccessRate = esr.GetValue(); enforcingSuccessRate > 100 {
|
if enforcingSuccessRate = uint32p(esr.GetValue()); *enforcingSuccessRate > 100 {
|
||||||
return nil, fmt.Errorf("outlier_detection.enforcing_success_rate = %v; must be <= 100", enforcingSuccessRate)
|
return nil, fmt.Errorf("outlier_detection.enforcing_success_rate = %v; must be <= 100", *enforcingSuccessRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
failurePercentageThreshold := uint32(defaultFailurePercentageThreshold)
|
var failurePercentageThreshold *uint32
|
||||||
if fpt := od.GetFailurePercentageThreshold(); fpt != nil {
|
if fpt := od.GetFailurePercentageThreshold(); fpt != nil {
|
||||||
if failurePercentageThreshold = fpt.GetValue(); failurePercentageThreshold > 100 {
|
if failurePercentageThreshold = uint32p(fpt.GetValue()); *failurePercentageThreshold > 100 {
|
||||||
return nil, fmt.Errorf("outlier_detection.failure_percentage_threshold = %v; must be <= 100", failurePercentageThreshold)
|
return nil, fmt.Errorf("outlier_detection.failure_percentage_threshold = %v; must be <= 100", *failurePercentageThreshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enforcingFailurePercentage := uint32(defaultEnforcingFailurePercentage)
|
// "If the enforcing_failure_percent field is set to 0 or null, the config
|
||||||
|
// failure_percent_ejection field will be null and all failure_percent_*
|
||||||
|
// fields will be ignored." - A50
|
||||||
|
var enforcingFailurePercentage *uint32
|
||||||
if efp := od.GetEnforcingFailurePercentage(); efp != nil {
|
if efp := od.GetEnforcingFailurePercentage(); efp != nil {
|
||||||
if enforcingFailurePercentage = efp.GetValue(); enforcingFailurePercentage > 100 {
|
if enforcingFailurePercentage = uint32p(efp.GetValue()); *enforcingFailurePercentage > 100 {
|
||||||
return nil, fmt.Errorf("outlier_detection.enforcing_failure_percentage = %v; must be <= 100", enforcingFailurePercentage)
|
return nil, fmt.Errorf("outlier_detection.enforcing_failure_percentage = %v; must be <= 100", *enforcingFailurePercentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
successRateStdevFactor := uint32(defaultSuccessRateStdevFactor)
|
var successRateStdevFactor *uint32
|
||||||
if srsf := od.GetSuccessRateStdevFactor(); srsf != nil {
|
if srsf := od.GetSuccessRateStdevFactor(); srsf != nil {
|
||||||
successRateStdevFactor = srsf.GetValue()
|
successRateStdevFactor = uint32p(srsf.GetValue())
|
||||||
}
|
}
|
||||||
successRateMinimumHosts := uint32(defaultSuccessRateMinimumHosts)
|
var successRateMinimumHosts *uint32
|
||||||
if srmh := od.GetSuccessRateMinimumHosts(); srmh != nil {
|
if srmh := od.GetSuccessRateMinimumHosts(); srmh != nil {
|
||||||
successRateMinimumHosts = srmh.GetValue()
|
successRateMinimumHosts = uint32p(srmh.GetValue())
|
||||||
}
|
}
|
||||||
successRateRequestVolume := uint32(defaultSuccessRateRequestVolume)
|
var successRateRequestVolume *uint32
|
||||||
if srrv := od.GetSuccessRateRequestVolume(); srrv != nil {
|
if srrv := od.GetSuccessRateRequestVolume(); srrv != nil {
|
||||||
successRateRequestVolume = srrv.GetValue()
|
successRateRequestVolume = uint32p(srrv.GetValue())
|
||||||
}
|
}
|
||||||
failurePercentageMinimumHosts := uint32(defaultFailurePercentageMinimumHosts)
|
var failurePercentageMinimumHosts *uint32
|
||||||
if fpmh := od.GetFailurePercentageMinimumHosts(); fpmh != nil {
|
if fpmh := od.GetFailurePercentageMinimumHosts(); fpmh != nil {
|
||||||
failurePercentageMinimumHosts = fpmh.GetValue()
|
failurePercentageMinimumHosts = uint32p(fpmh.GetValue())
|
||||||
}
|
}
|
||||||
failurePercentageRequestVolume := uint32(defaultFailurePercentageRequestVolume)
|
var failurePercentageRequestVolume *uint32
|
||||||
if fprv := od.GetFailurePercentageRequestVolume(); fprv != nil {
|
if fprv := od.GetFailurePercentageRequestVolume(); fprv != nil {
|
||||||
failurePercentageRequestVolume = fprv.GetValue()
|
failurePercentageRequestVolume = uint32p(fprv.GetValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &OutlierDetection{
|
// "if the enforcing_success_rate field is set to 0, the config
|
||||||
Interval: interval,
|
// success_rate_ejection field will be null and all success_rate_* fields
|
||||||
BaseEjectionTime: baseEjectionTime,
|
// will be ignored." - A50
|
||||||
MaxEjectionTime: maxEjectionTime,
|
var sre *successRateEjection
|
||||||
MaxEjectionPercent: maxEjectionPercent,
|
if enforcingSuccessRate == nil || *enforcingSuccessRate != 0 {
|
||||||
EnforcingSuccessRate: enforcingSuccessRate,
|
sre = &successRateEjection{
|
||||||
FailurePercentageThreshold: failurePercentageThreshold,
|
StdevFactor: successRateStdevFactor,
|
||||||
EnforcingFailurePercentage: enforcingFailurePercentage,
|
EnforcementPercentage: enforcingSuccessRate,
|
||||||
SuccessRateStdevFactor: successRateStdevFactor,
|
MinimumHosts: successRateMinimumHosts,
|
||||||
SuccessRateMinimumHosts: successRateMinimumHosts,
|
RequestVolume: successRateRequestVolume,
|
||||||
SuccessRateRequestVolume: successRateRequestVolume,
|
}
|
||||||
FailurePercentageMinimumHosts: failurePercentageMinimumHosts,
|
}
|
||||||
FailurePercentageRequestVolume: failurePercentageRequestVolume,
|
|
||||||
}, nil
|
// "If the enforcing_failure_percent field is set to 0 or null, the config
|
||||||
|
// failure_percent_ejection field will be null and all failure_percent_*
|
||||||
|
// fields will be ignored." - A50
|
||||||
|
var fpe *failurePercentageEjection
|
||||||
|
if enforcingFailurePercentage != nil && *enforcingFailurePercentage != 0 {
|
||||||
|
fpe = &failurePercentageEjection{
|
||||||
|
Threshold: failurePercentageThreshold,
|
||||||
|
EnforcementPercentage: enforcingFailurePercentage,
|
||||||
|
MinimumHosts: failurePercentageMinimumHosts,
|
||||||
|
RequestVolume: failurePercentageRequestVolume,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
odLBCfg := &odLBConfig{
|
||||||
|
Interval: interval,
|
||||||
|
BaseEjectionTime: baseEjectionTime,
|
||||||
|
MaxEjectionTime: maxEjectionTime,
|
||||||
|
MaxEjectionPercent: maxEjectionPercent,
|
||||||
|
SuccessRateEjection: sre,
|
||||||
|
FailurePercentageEjection: fpe,
|
||||||
|
}
|
||||||
|
return json.Marshal(odLBCfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
package xdsresource
|
package xdsresource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
@ -30,8 +30,6 @@ import (
|
||||||
"google.golang.org/grpc/internal/testutils"
|
"google.golang.org/grpc/internal/testutils"
|
||||||
"google.golang.org/grpc/internal/xds/matcher"
|
"google.golang.org/grpc/internal/xds/matcher"
|
||||||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
|
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
|
||||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
||||||
|
|
||||||
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
||||||
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
@ -43,6 +41,8 @@ import (
|
||||||
v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
||||||
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
||||||
anypb "github.com/golang/protobuf/ptypes/any"
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1382,43 +1382,55 @@ func (s) TestValidateClusterWithOutlierDetection(t *testing.T) {
|
||||||
OutlierDetection: od,
|
OutlierDetection: od,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
odToClusterUpdate := func(od *OutlierDetection) ClusterUpdate {
|
|
||||||
return ClusterUpdate{
|
|
||||||
ClusterName: clusterName,
|
|
||||||
LRSServerConfig: ClusterLRSOff,
|
|
||||||
OutlierDetection: od,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cluster *v3clusterpb.Cluster
|
cluster *v3clusterpb.Cluster
|
||||||
wantUpdate ClusterUpdate
|
wantODCfg string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "successful-case-all-defaults",
|
name: "success-and-failure-null",
|
||||||
// Outlier detection proto is present without any fields specified,
|
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{}),
|
||||||
// so should trigger all default values in the update.
|
wantODCfg: `{"successRateEjection": {}}`,
|
||||||
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{}),
|
|
||||||
wantUpdate: odToClusterUpdate(&OutlierDetection{
|
|
||||||
Interval: 10 * time.Second,
|
|
||||||
BaseEjectionTime: 30 * time.Second,
|
|
||||||
MaxEjectionTime: 300 * time.Second,
|
|
||||||
MaxEjectionPercent: 10,
|
|
||||||
SuccessRateStdevFactor: 1900,
|
|
||||||
EnforcingSuccessRate: 100,
|
|
||||||
SuccessRateMinimumHosts: 5,
|
|
||||||
SuccessRateRequestVolume: 100,
|
|
||||||
FailurePercentageThreshold: 85,
|
|
||||||
EnforcingFailurePercentage: 0,
|
|
||||||
FailurePercentageMinimumHosts: 5,
|
|
||||||
FailurePercentageRequestVolume: 50,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "successful-case-all-fields-configured-and-valid",
|
name: "success-and-failure-zero",
|
||||||
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
|
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
|
||||||
|
EnforcingSuccessRate: &wrapperspb.UInt32Value{Value: 0}, // Thus doesn't create sre - to focus on fpe
|
||||||
|
EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 0},
|
||||||
|
}),
|
||||||
|
wantODCfg: `{}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "some-fields-set",
|
||||||
|
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
|
||||||
|
Interval: &durationpb.Duration{Seconds: 1},
|
||||||
|
MaxEjectionTime: &durationpb.Duration{Seconds: 3},
|
||||||
|
EnforcingSuccessRate: &wrapperspb.UInt32Value{Value: 3},
|
||||||
|
SuccessRateRequestVolume: &wrapperspb.UInt32Value{Value: 5},
|
||||||
|
EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 7},
|
||||||
|
FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 9},
|
||||||
|
}),
|
||||||
|
wantODCfg: `{
|
||||||
|
"interval": "1s",
|
||||||
|
"maxEjectionTime": "3s",
|
||||||
|
"successRateEjection": {
|
||||||
|
"enforcementPercentage": 3,
|
||||||
|
"requestVolume": 5
|
||||||
|
},
|
||||||
|
"failurePercentageEjection": {
|
||||||
|
"enforcementPercentage": 7,
|
||||||
|
"requestVolume": 9
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "every-field-set-non-zero",
|
||||||
|
cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
|
||||||
|
// all fields set (including ones that will be layered) should
|
||||||
|
// pick up those too and explicitly all fields, including those
|
||||||
|
// put in layers, in the JSON generated.
|
||||||
Interval: &durationpb.Duration{Seconds: 1},
|
Interval: &durationpb.Duration{Seconds: 1},
|
||||||
BaseEjectionTime: &durationpb.Duration{Seconds: 2},
|
BaseEjectionTime: &durationpb.Duration{Seconds: 2},
|
||||||
MaxEjectionTime: &durationpb.Duration{Seconds: 3},
|
MaxEjectionTime: &durationpb.Duration{Seconds: 3},
|
||||||
|
@ -1432,20 +1444,24 @@ func (s) TestValidateClusterWithOutlierDetection(t *testing.T) {
|
||||||
FailurePercentageMinimumHosts: &wrapperspb.UInt32Value{Value: 8},
|
FailurePercentageMinimumHosts: &wrapperspb.UInt32Value{Value: 8},
|
||||||
FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 9},
|
FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 9},
|
||||||
}),
|
}),
|
||||||
wantUpdate: odToClusterUpdate(&OutlierDetection{
|
wantODCfg: `{
|
||||||
Interval: time.Second,
|
"interval": "1s",
|
||||||
BaseEjectionTime: time.Second * 2,
|
"baseEjectionTime": "2s",
|
||||||
MaxEjectionTime: time.Second * 3,
|
"maxEjectionTime": "3s",
|
||||||
MaxEjectionPercent: 1,
|
"maxEjectionPercent": 1,
|
||||||
SuccessRateStdevFactor: 2,
|
"successRateEjection": {
|
||||||
EnforcingSuccessRate: 3,
|
"stdevFactor": 2,
|
||||||
SuccessRateMinimumHosts: 4,
|
"enforcementPercentage": 3,
|
||||||
SuccessRateRequestVolume: 5,
|
"minimumHosts": 4,
|
||||||
FailurePercentageThreshold: 6,
|
"requestVolume": 5
|
||||||
EnforcingFailurePercentage: 7,
|
},
|
||||||
FailurePercentageMinimumHosts: 8,
|
"failurePercentageEjection": {
|
||||||
FailurePercentageRequestVolume: 9,
|
"threshold": 6,
|
||||||
}),
|
"enforcementPercentage": 7,
|
||||||
|
"minimumHosts": 8,
|
||||||
|
"requestVolume": 9
|
||||||
|
}
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "interval-is-negative",
|
name: "interval-is-negative",
|
||||||
|
@ -1507,8 +1523,21 @@ func (s) TestValidateClusterWithOutlierDetection(t *testing.T) {
|
||||||
if (err != nil) != test.wantErr {
|
if (err != nil) != test.wantErr {
|
||||||
t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr)
|
t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr)
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(test.wantUpdate, update, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(ClusterUpdate{}, "LBPolicy")); diff != "" {
|
if test.wantErr {
|
||||||
t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, +got):\n%s", diff)
|
return
|
||||||
|
}
|
||||||
|
// got and want must be unmarshalled since JSON strings shouldn't
|
||||||
|
// generally be directly compared.
|
||||||
|
var got map[string]interface{}
|
||||||
|
if err := json.Unmarshal(update.OutlierDetection, &got); err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling update.OutlierDetection (%q): %v", update.OutlierDetection, err)
|
||||||
|
}
|
||||||
|
var want map[string]interface{}
|
||||||
|
if err := json.Unmarshal(json.RawMessage(test.wantODCfg), &want); err != nil {
|
||||||
|
t.Fatalf("Error unmarshalling wantODCfg (%q): %v", test.wantODCfg, err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(got, want); diff != "" {
|
||||||
|
t.Fatalf("cluster.OutlierDetection got unexpected output, diff (-got, +want): %v", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue