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  }