github.com/cloudwego/kitex@v0.9.0/pkg/circuitbreak/cbsuite_test.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package circuitbreak 18 19 import ( 20 "context" 21 "errors" 22 "net" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/bytedance/gopkg/cloud/circuitbreaker" 28 29 "github.com/cloudwego/kitex/internal/test" 30 kd "github.com/cloudwego/kitex/pkg/discovery" 31 "github.com/cloudwego/kitex/pkg/endpoint" 32 "github.com/cloudwego/kitex/pkg/event" 33 "github.com/cloudwego/kitex/pkg/kerrors" 34 "github.com/cloudwego/kitex/pkg/rpcinfo" 35 "github.com/cloudwego/kitex/pkg/utils" 36 ) 37 38 var ( 39 errMock = errors.New("mock error") 40 addrMock = utils.NewNetAddr("mock", "127.0.0.1:8888") 41 ) 42 43 func TestNewCBSuite(t *testing.T) { 44 cb := NewCBSuite(RPCInfo2Key) 45 test.Assert(t, cb.servicePanel == nil) 46 test.Assert(t, cb.instancePanel == nil) 47 48 var mws []endpoint.Middleware 49 mws = append(mws, cb.ServiceCBMW()) 50 mws = append(mws, cb.InstanceCBMW()) 51 test.Assert(t, cb.instancePanel != nil) 52 test.Assert(t, cb.servicePanel != nil) 53 54 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 55 return nil 56 }) 57 ctx := prepareCtx() 58 err := eps(ctx, nil, nil) 59 test.Assert(t, err == nil) 60 61 eps = endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 62 return errMock 63 }) 64 err = eps(ctx, nil, nil) 65 test.Assert(t, err.Error() == errMock.Error(), err) 66 67 cb.Close() 68 test.Assert(t, cb.instancePanel == nil) 69 test.Assert(t, cb.servicePanel == nil) 70 } 71 72 func TestGetServiceCB(t *testing.T) { 73 cb := NewCBSuite(RPCInfo2Key) 74 cb.ServicePanel() 75 cb.ServiceControl() 76 test.Assert(t, cb.servicePanel != nil) 77 test.Assert(t, cb.serviceControl != nil) 78 } 79 80 func TestServiceCB(t *testing.T) { 81 cb := NewCBSuite(RPCInfo2Key) 82 cb.initServiceCB() 83 opts := circuitbreaker.Options{ 84 ShouldTripWithKey: cb.svcTripFunc, 85 CoolingTimeout: 100 * time.Millisecond, 86 DetectTimeout: 100 * time.Millisecond, 87 } 88 cb.servicePanel, _ = circuitbreaker.NewPanel(cb.onServiceStateChange, opts) 89 90 var mws []endpoint.Middleware 91 mws = append(mws, cb.ServiceCBMW()) 92 mws = append(mws, cb.InstanceCBMW()) 93 ctx := prepareCtx() 94 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 95 return kerrors.ErrRPCTimeout.WithCause(errMock) 96 }) 97 succEps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 98 return nil 99 }) 100 for i := 0; i < 300; i++ { 101 err := eps(ctx, nil, nil) 102 if i < 200 { 103 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout), err, i) 104 } else { 105 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 106 } 107 } 108 109 cfgKey := RPCInfo2Key(rpcinfo.GetRPCInfo(ctx)) 110 cb.UpdateServiceCBConfig(cfgKey, CBConfig{Enable: false}) 111 // disable circuit breaker 112 for i := 0; i < 300; i++ { 113 err := eps(ctx, nil, nil) 114 if i < 200 { 115 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout), err, i) 116 } 117 } 118 119 // enable circuit breaker 120 cb.UpdateServiceCBConfig(cfgKey, CBConfig{ 121 Enable: true, 122 ErrRate: 0.1, 123 MinSample: 100, 124 }) 125 // recover 126 time.Sleep(200 * time.Millisecond) 127 for i := 0; i < 300; i++ { 128 err := succEps(ctx, nil, nil) 129 if i == 2 { 130 time.Sleep(200 * time.Millisecond) 131 } 132 if i == 1 || i == 2 { 133 // half open: within detect timeout, still return cb err 134 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 135 } else { 136 // i == 0 : after cooling timeout, half open 137 test.Assert(t, err == nil, err) 138 } 139 } 140 cb.Close() 141 } 142 143 func TestUpdateConfigErrRate(t *testing.T) { 144 cb := NewCBSuite(RPCInfo2Key) 145 opts := circuitbreaker.Options{ 146 ShouldTripWithKey: cb.svcTripFunc, 147 CoolingTimeout: 100 * time.Millisecond, 148 DetectTimeout: 100 * time.Millisecond, 149 } 150 cb.servicePanel, _ = circuitbreaker.NewPanel(cb.onServiceStateChange, opts) 151 152 var mws []endpoint.Middleware 153 mws = append(mws, cb.ServiceCBMW()) 154 mws = append(mws, cb.InstanceCBMW()) 155 ctx := prepareCtx() 156 count := 0 157 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 158 if count%10 == 0 { 159 return kerrors.ErrRPCTimeout.WithCause(errMock) 160 } 161 return 162 }) 163 cfgKey := RPCInfo2Key(rpcinfo.GetRPCInfo(ctx)) 164 cb.UpdateServiceCBConfig(cfgKey, CBConfig{ 165 Enable: true, 166 ErrRate: 0.1, 167 MinSample: 100, 168 }) 169 for i := 0; i < 300; i++ { 170 err := eps(ctx, nil, nil) 171 if i < 100 { 172 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout), err, i) 173 } else { 174 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 175 } 176 } 177 cb.Close() 178 } 179 180 func TestUpdateConfigSamples(t *testing.T) { 181 cb := NewCBSuite(nil) 182 opts := circuitbreaker.Options{ 183 ShouldTripWithKey: cb.svcTripFunc, 184 CoolingTimeout: 100 * time.Millisecond, 185 DetectTimeout: 100 * time.Millisecond, 186 } 187 cb.servicePanel, _ = circuitbreaker.NewPanel(cb.onServiceStateChange, opts) 188 189 var mws []endpoint.Middleware 190 mws = append(mws, cb.ServiceCBMW()) 191 mws = append(mws, cb.InstanceCBMW()) 192 ctx := prepareCtx() 193 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 194 return kerrors.ErrRPCTimeout.WithCause(errMock) 195 }) 196 cfgKey := RPCInfo2Key(rpcinfo.GetRPCInfo(ctx)) 197 cb.UpdateServiceCBConfig(cfgKey, CBConfig{ 198 Enable: true, 199 ErrRate: 0.5, 200 MinSample: 100, 201 }) 202 for i := 0; i < 300; i++ { 203 err := eps(ctx, nil, nil) 204 if i < 100 { 205 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout), err, i) 206 } else { 207 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 208 } 209 } 210 cb.Close() 211 } 212 213 func TestDisableCB(t *testing.T) { 214 cb := NewCBSuite(RPCInfo2Key) 215 var mws []endpoint.Middleware 216 mws = append(mws, cb.ServiceCBMW()) 217 mws = append(mws, cb.InstanceCBMW()) 218 ctx := prepareCtx() 219 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 220 return kerrors.ErrRPCTimeout.WithCause(errMock) 221 }) 222 for i := 0; i < 300; i++ { 223 err := eps(ctx, nil, nil) 224 if i < 200 { 225 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout), err, i) 226 } else { 227 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 228 } 229 } 230 cb.Close() 231 } 232 233 func TestInstanceCB(t *testing.T) { 234 cb := NewCBSuite(RPCInfo2Key) 235 var mws []endpoint.Middleware 236 mws = append(mws, cb.InstanceCBMW()) 237 ctx := prepareCtx() 238 count := 0 239 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 240 if count%10 == 0 { 241 return kerrors.ErrGetConnection.WithCause(errMock) 242 } 243 return 244 }) 245 cfgKey := RPCInfo2Key(rpcinfo.GetRPCInfo(ctx)) 246 cb.UpdateServiceCBConfig(cfgKey, CBConfig{ 247 Enable: true, 248 ErrRate: 0.1, 249 MinSample: 100, 250 }) 251 cb.UpdateInstanceCBConfig(CBConfig{ 252 Enable: true, 253 ErrRate: 0.1, 254 MinSample: 100, 255 }) 256 for i := 0; i < 300; i++ { 257 err := eps(ctx, nil, nil) 258 if i < 100 { 259 test.Assert(t, errors.Is(err, kerrors.ErrGetConnection), err, i) 260 } else { 261 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 262 } 263 } 264 cb.Close() 265 } 266 267 func TestCBSuite_AddEvent4CB(t *testing.T) { 268 cb := NewCBSuite(RPCInfo2Key) 269 cb.SetEventBusAndQueue(event.NewEventBus(), event.NewQueue(event.MaxEventNum)) 270 var mws []endpoint.Middleware 271 mws = append(mws, cb.InstanceCBMW()) 272 ctx := prepareCtx() 273 count := 0 274 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 275 if count%10 == 0 { 276 return kerrors.ErrGetConnection.WithCause(errMock) 277 } 278 return 279 }) 280 for i := 0; i < 300; i++ { 281 err := eps(ctx, nil, nil) 282 if i < 200 { 283 test.Assert(t, errors.Is(err, kerrors.ErrGetConnection), err, i) 284 } else { 285 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 286 } 287 } 288 cb.Close() 289 } 290 291 func TestRemoveInstBreaker(t *testing.T) { 292 cb := NewCBSuite(RPCInfo2Key) 293 bus := event.NewEventBus() 294 queue := event.NewQueue(event.MaxEventNum) 295 cb.SetEventBusAndQueue(bus, queue) 296 297 var mws []endpoint.Middleware 298 mws = append(mws, cb.InstanceCBMW()) 299 ctx := prepareCtx() 300 count := 0 301 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 302 if count%10 == 0 { 303 return kerrors.ErrGetConnection.WithCause(errMock) 304 } 305 return 306 }) 307 for i := 0; i < 300; i++ { 308 err := eps(ctx, nil, nil) 309 if i < 200 { 310 test.Assert(t, errors.Is(err, kerrors.ErrGetConnection), err, i) 311 } else { 312 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 313 } 314 } 315 test.Assert(t, len(cb.instancePanel.DumpBreakers()) == 1) 316 instCBKey := addrMock.String() 317 _, ok := cb.instancePanel.DumpBreakers()[instCBKey] 318 test.Assert(t, ok) 319 320 now := time.Now() 321 bus.Dispatch(&event.Event{ 322 Name: kd.ChangeEventName, 323 Time: now, 324 Extra: &kd.Change{ 325 Removed: []kd.Instance{inst}, 326 }, 327 }) 328 time.Sleep(10 * time.Millisecond) 329 _, ok = cb.instancePanel.DumpBreakers()[instCBKey] 330 test.Assert(t, !ok) 331 test.Assert(t, len(cb.instancePanel.DumpBreakers()) == 0) 332 cb.Close() 333 } 334 335 func TestCBSuite_Dump(t *testing.T) { 336 cb := NewCBSuite(RPCInfo2Key) 337 bus := event.NewEventBus() 338 queue := event.NewQueue(event.MaxEventNum) 339 cb.SetEventBusAndQueue(bus, queue) 340 341 var mws []endpoint.Middleware 342 mws = append(mws, cb.ServiceCBMW()) 343 mws = append(mws, cb.InstanceCBMW()) 344 ctx := prepareCtx() 345 count := 0 346 eps := endpoint.Chain(mws...)(func(ctx context.Context, req, resp interface{}) (err error) { 347 if count%10 == 0 { 348 return kerrors.ErrGetConnection.WithCause(errMock) 349 } 350 return 351 }) 352 for i := 0; i < 300; i++ { 353 err := eps(ctx, nil, nil) 354 if i < 200 { 355 test.Assert(t, errors.Is(err, kerrors.ErrGetConnection), err, i) 356 } else { 357 test.Assert(t, errors.Is(err, kerrors.ErrCircuitBreak), err, i) 358 } 359 } 360 cfgKey := RPCInfo2Key(rpcinfo.GetRPCInfo(ctx)) 361 cbDump := cb.Dump().(map[string]interface{}) 362 test.Assert(t, len(cbDump) == 3) 363 cbCfg := cbDump[cbConfig].(map[string]interface{}) 364 test.Assert(t, len(cbCfg) == 2, len(cbCfg), cbCfg) 365 366 instCfg, ok := cbCfg[instanceCBKey].(CBConfig) 367 test.Assert(t, ok) 368 test.Assert(t, instCfg.Enable) 369 test.Assert(t, instCfg.MinSample == GetDefaultCBConfig().MinSample) 370 test.Assert(t, instCfg.ErrRate == GetDefaultCBConfig().ErrRate) 371 372 svcCfg, ok := cbCfg[serviceCBKey].(map[string]interface{}) 373 test.Assert(t, ok) 374 cfg, ok := svcCfg[cfgKey].(CBConfig) 375 test.Assert(t, ok) 376 test.Assert(t, cfg.Enable) 377 test.Assert(t, cfg.MinSample == GetDefaultCBConfig().MinSample) 378 test.Assert(t, cfg.ErrRate == GetDefaultCBConfig().ErrRate) 379 380 // update config, then dump 381 newCfg := CBConfig{ 382 Enable: true, 383 ErrRate: 0.1, 384 MinSample: 100, 385 } 386 cb.UpdateServiceCBConfig(cfgKey, newCfg) 387 cb.UpdateInstanceCBConfig(newCfg) 388 cbDump = cb.Dump().(map[string]interface{}) 389 test.Assert(t, len(cbDump) == 3) 390 391 cbCfg = cbDump[cbConfig].(map[string]interface{}) 392 test.Assert(t, len(cbCfg) == 2, len(cbCfg), cbCfg) 393 394 instCfg, ok = cbCfg[instanceCBKey].(CBConfig) 395 test.Assert(t, ok) 396 test.Assert(t, instCfg.Enable) 397 test.Assert(t, instCfg.MinSample == newCfg.MinSample) 398 test.Assert(t, instCfg.ErrRate == newCfg.ErrRate) 399 400 svcCfg, ok = cbCfg[serviceCBKey].(map[string]interface{}) 401 test.Assert(t, ok) 402 cfg, ok = svcCfg[cfgKey].(CBConfig) 403 test.Assert(t, ok) 404 test.Assert(t, cfg.Enable) 405 test.Assert(t, cfg.MinSample == newCfg.MinSample) 406 test.Assert(t, cfg.ErrRate == newCfg.ErrRate) 407 408 cb.Close() 409 } 410 411 func prepareCtx() context.Context { 412 from := rpcinfo.NewEndpointInfo("caller", "", nil, nil) 413 to := rpcinfo.NewEndpointInfo("callee", "method", addrMock, nil) 414 ri := rpcinfo.NewRPCInfo(from, to, nil, nil, nil) 415 ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri) 416 return ctx 417 } 418 419 var inst kd.Instance = &mockInst{} 420 421 type mockInst struct{} 422 423 func (m mockInst) Address() net.Addr { 424 return addrMock 425 } 426 427 func (m mockInst) Weight() int { 428 return 10 429 } 430 431 func (m mockInst) Tag(key string) (value string, exist bool) { 432 return 433 } 434 435 func TestCBConfig_DeepCopy(t *testing.T) { 436 type fields struct { 437 c *CBConfig 438 } 439 tests := []struct { 440 name string 441 fields fields 442 want *CBConfig 443 }{ 444 { 445 name: "test_nil_copy", 446 fields: fields{ 447 c: nil, 448 }, 449 want: nil, 450 }, 451 { 452 name: "test_all_copy", 453 fields: fields{ 454 c: &CBConfig{ 455 Enable: true, 456 ErrRate: 0.1, 457 MinSample: 10, 458 }, 459 }, 460 want: &CBConfig{ 461 Enable: true, 462 ErrRate: 0.1, 463 MinSample: 10, 464 }, 465 }, 466 } 467 for _, tt := range tests { 468 t.Run(tt.name, func(t *testing.T) { 469 if got := tt.fields.c.DeepCopy(); !reflect.DeepEqual(got, tt.want) { 470 t.Errorf("DeepCopy() = %v, want %v", got, tt.want) 471 } 472 }) 473 } 474 } 475 476 func TestCBConfig_Equals(t *testing.T) { 477 type fields struct { 478 c *CBConfig 479 } 480 type args struct { 481 other *CBConfig 482 } 483 tests := []struct { 484 name string 485 fields fields 486 args args 487 want bool 488 }{ 489 { 490 name: "test_nil_equal", 491 fields: fields{ 492 c: nil, 493 }, 494 args: args{ 495 other: nil, 496 }, 497 want: true, 498 }, 499 { 500 name: "test_nil_not_equal", 501 fields: fields{ 502 c: nil, 503 }, 504 args: args{ 505 other: &CBConfig{ 506 Enable: true, 507 ErrRate: 0.1, 508 MinSample: 10, 509 }, 510 }, 511 want: false, 512 }, 513 { 514 name: "test_other_nil_not_equal", 515 fields: fields{ 516 c: &CBConfig{ 517 Enable: true, 518 ErrRate: 0.1, 519 MinSample: 10, 520 }, 521 }, 522 args: args{ 523 other: nil, 524 }, 525 want: false, 526 }, 527 { 528 name: "test_all_equal", 529 fields: fields{ 530 c: &CBConfig{ 531 Enable: true, 532 ErrRate: 0.1, 533 MinSample: 10, 534 }, 535 }, 536 args: args{ 537 other: &CBConfig{ 538 Enable: true, 539 ErrRate: 0.1, 540 MinSample: 10, 541 }, 542 }, 543 want: true, 544 }, 545 { 546 name: "test_all_not_equal", 547 fields: fields{ 548 c: &CBConfig{ 549 Enable: true, 550 ErrRate: 0.1, 551 MinSample: 10, 552 }, 553 }, 554 args: args{ 555 other: &CBConfig{ 556 Enable: false, 557 ErrRate: 0.2, 558 MinSample: 20, 559 }, 560 }, 561 want: false, 562 }, 563 { 564 name: "test_enable_equal", 565 fields: fields{ 566 c: &CBConfig{ 567 Enable: true, 568 ErrRate: 0.1, 569 MinSample: 10, 570 }, 571 }, 572 args: args{ 573 other: &CBConfig{ 574 Enable: true, 575 ErrRate: 0.2, 576 MinSample: 20, 577 }, 578 }, 579 want: false, 580 }, 581 { 582 name: "test_err_rate_equal", 583 fields: fields{ 584 c: &CBConfig{ 585 Enable: true, 586 ErrRate: 0.1, 587 MinSample: 10, 588 }, 589 }, 590 args: args{ 591 other: &CBConfig{ 592 Enable: false, 593 ErrRate: 0.1, 594 MinSample: 20, 595 }, 596 }, 597 want: false, 598 }, 599 { 600 name: "test_min_sample_equal", 601 fields: fields{ 602 c: &CBConfig{ 603 Enable: true, 604 ErrRate: 0.1, 605 MinSample: 10, 606 }, 607 }, 608 args: args{ 609 other: &CBConfig{ 610 Enable: false, 611 ErrRate: 0.2, 612 MinSample: 10, 613 }, 614 }, 615 want: false, 616 }, 617 } 618 for _, tt := range tests { 619 t.Run(tt.name, func(t *testing.T) { 620 if got := tt.fields.c.Equals(tt.args.other); got != tt.want { 621 t.Errorf("Equals() = %v, want %v", got, tt.want) 622 } 623 }) 624 } 625 }