From 7f1c86602e8d63d084a283986f5fbb9a357bf6d4 Mon Sep 17 00:00:00 2001 From: sanxun0325 Date: Thu, 24 Sep 2020 22:24:48 +0800 Subject: [PATCH 1/4] commit rule cache & check --- core/flow/rule_manager.go | 7 ++++++- core/flow/rule_manager_test.go | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/flow/rule_manager.go b/core/flow/rule_manager.go index 37f3fe5de..45aca6339 100644 --- a/core/flow/rule_manager.go +++ b/core/flow/rule_manager.go @@ -2,6 +2,7 @@ package flow import ( "fmt" + "reflect" "sync" "github.com/alibaba/sentinel-golang/logging" @@ -24,6 +25,7 @@ var ( tcGenFuncMap = make(map[trafficControllerGenKey]TrafficControllerGenFunc) tcMap = make(TrafficControllerMap) tcMux = new(sync.RWMutex) + currentRules = make([]*Rule, 0) ) func init() { @@ -75,7 +77,6 @@ func onRuleUpdate(rules []*Rule) (err error) { }() m := buildFlowMap(rules) - start := util.CurrentTimeNano() tcMux.Lock() defer func() { @@ -88,12 +89,16 @@ func onRuleUpdate(rules []*Rule) (err error) { }() tcMap = m + currentRules = rules return nil } // LoadRules loads the given flow rules to the rule manager, while all previous rules will be replaced. func LoadRules(rules []*Rule) (bool, error) { // TODO: rethink the design + if isEqual := reflect.DeepEqual(currentRules, rules); isEqual { + return isEqual, errors.New("The current Rules is the same as the rules to be loaded") + } err := onRuleUpdate(rules) return true, err } diff --git a/core/flow/rule_manager_test.go b/core/flow/rule_manager_test.go index 4768f8e25..1a1204b07 100644 --- a/core/flow/rule_manager_test.go +++ b/core/flow/rule_manager_test.go @@ -147,3 +147,26 @@ func TestGetRules(t *testing.T) { } }) } + +func TestLoadRules(t *testing.T) { + t.Run("loadSameRules", func(t *testing.T) { + _, err := LoadRules([]*Rule{ + { + Resource: "some-test", + Threshold: 10, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + }, + }) + assert.Nil(t, err) + _, err = LoadRules([]*Rule{ + { + Resource: "some-test", + Threshold: 10, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + }, + }) + assert.Error(t, err, "The current Rules is the same as the rules to be loaded") + }) +} From 8231ccb8d28106472cd78ffe46686e17f4145617 Mon Sep 17 00:00:00 2001 From: sanxun0325 Date: Thu, 24 Sep 2020 22:56:21 +0800 Subject: [PATCH 2/4] commit --- adapter/micro/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapter/micro/server_test.go b/adapter/micro/server_test.go index 41ae8883a..cb3074eec 100644 --- a/adapter/micro/server_test.go +++ b/adapter/micro/server_test.go @@ -58,7 +58,7 @@ func TestServerLimiter(t *testing.T) { _, err = flow.LoadRules([]*flow.Rule{ { Resource: req.Method(), - Threshold: 1, + Threshold: 0, TokenCalculateStrategy: flow.Direct, ControlBehavior: flow.Reject, }, From 44605bca55f39a8b24323b1ea62773cbda277e02 Mon Sep 17 00:00:00 2001 From: sanxun0325 Date: Fri, 25 Sep 2020 22:32:39 +0800 Subject: [PATCH 3/4] update result --- core/flow/rule_manager.go | 3 ++- core/flow/rule_manager_test.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/flow/rule_manager.go b/core/flow/rule_manager.go index 45aca6339..de80c4db6 100644 --- a/core/flow/rule_manager.go +++ b/core/flow/rule_manager.go @@ -96,8 +96,9 @@ func onRuleUpdate(rules []*Rule) (err error) { // LoadRules loads the given flow rules to the rule manager, while all previous rules will be replaced. func LoadRules(rules []*Rule) (bool, error) { // TODO: rethink the design + //check the current rules is the same as the rules to be loaded if isEqual := reflect.DeepEqual(currentRules, rules); isEqual { - return isEqual, errors.New("The current Rules is the same as the rules to be loaded") + return false, nil } err := onRuleUpdate(rules) return true, err diff --git a/core/flow/rule_manager_test.go b/core/flow/rule_manager_test.go index 1a1204b07..1faed8243 100644 --- a/core/flow/rule_manager_test.go +++ b/core/flow/rule_manager_test.go @@ -159,7 +159,7 @@ func TestLoadRules(t *testing.T) { }, }) assert.Nil(t, err) - _, err = LoadRules([]*Rule{ + ok, err := LoadRules([]*Rule{ { Resource: "some-test", Threshold: 10, @@ -167,6 +167,7 @@ func TestLoadRules(t *testing.T) { ControlBehavior: Reject, }, }) - assert.Error(t, err, "The current Rules is the same as the rules to be loaded") + assert.Nil(t, err) + assert.False(t, ok) }) } From 1a3841af072e504b815af4c468a5504500efe98c Mon Sep 17 00:00:00 2001 From: sanxun0325 Date: Wed, 7 Oct 2020 23:02:38 +0800 Subject: [PATCH 4/4] commit flow rule cache --- .github/workflows/go.yml | 60 ++ .travis.yml | 20 - README.md | 4 + adapter/dubbo/consumer_filter.go | 57 -- adapter/dubbo/consumer_filter_test.go | 26 - adapter/dubbo/fallback.go | 30 - adapter/dubbo/filter.go | 19 - adapter/dubbo/provider_filter.go | 55 -- adapter/dubbo/provider_filter_test.go | 26 - adapter/dubbo/util.go | 70 --- adapter/dubbo/util_test.go | 24 - adapter/echo/middleware_test.go | 20 +- adapter/gin/middleware_test.go | 20 +- adapter/grpc/client_test.go | 40 +- adapter/grpc/server_test.go | 42 +- adapter/micro/client_test.go | 10 +- adapter/micro/doc.go | 21 + adapter/micro/server_test.go | 20 +- api/api.go | 18 +- api/api_test.go | 4 +- api/doc.go | 50 ++ api/init.go | 15 +- api/slot_chain.go | 13 +- api/tracer.go | 3 +- core/base/context.go | 8 +- core/base/entry.go | 5 +- core/base/result.go | 3 + core/base/slot_chain.go | 12 +- core/base/slot_chain_test.go | 24 +- core/base/stat.go | 49 +- core/base/stat_test.go | 25 +- core/circuitbreaker/circuit_breaker.go | 27 +- core/circuitbreaker/circuit_breaker_test.go | 316 ++++++++++ core/circuitbreaker/doc.go | 113 ++++ core/circuitbreaker/rule.go | 2 +- core/circuitbreaker/rule_manager.go | 70 ++- core/circuitbreaker/rule_manager_test.go | 108 ++++ core/circuitbreaker/slot.go | 2 +- core/circuitbreaker/slot_test.go | 121 ++++ core/circuitbreaker/stat_slot.go | 2 +- core/config/config.go | 44 +- core/config/entity.go | 47 +- core/flow/doc.go | 16 + core/flow/rule.go | 147 +++-- core/flow/rule_manager.go | 422 ++++++++++--- core/flow/rule_manager_test.go | 396 ++++++++++++- core/flow/slot.go | 35 +- core/flow/slot_test.go | 54 ++ core/flow/standalone_stat_slot.go | 31 + core/flow/tc_default.go | 46 +- core/flow/tc_throttling.go | 21 +- core/flow/tc_throttling_test.go | 6 +- core/flow/tc_warm_up.go | 26 +- core/flow/traffic_shaping.go | 37 +- core/hotspot/cache/lru.go | 3 - core/hotspot/concurrency_stat_slot.go | 4 +- core/hotspot/rule.go | 11 +- core/hotspot/rule_manager.go | 51 +- core/hotspot/rule_manager_test.go | 8 +- core/hotspot/slot.go | 16 +- core/hotspot/slot_test.go | 4 +- core/hotspot/traffic_shaping.go | 31 +- core/isolation/doc.go | 3 + core/isolation/rule.go | 45 ++ core/isolation/rule_manager.go | 138 +++++ core/isolation/rule_manager_test.go | 39 ++ core/isolation/slot.go | 47 ++ core/log/metric/aggregator.go | 4 +- core/log/metric/reader.go | 4 +- core/log/metric/searcher.go | 6 +- core/log/metric/writer.go | 29 +- core/log/slot.go | 8 +- core/stat/base/bucket_leap_array.go | 22 +- core/stat/base/leap_array.go | 4 +- core/stat/base/leap_array_test.go | 14 +- core/stat/base/metric_bucket_test.go | 1 + core/stat/base/sliding_window_metric.go | 51 +- core/stat/base/sliding_window_metric_test.go | 94 +-- core/stat/base_node.go | 15 +- core/stat/node_storage.go | 2 +- core/stat/resource_node.go | 43 +- core/stat/resource_node_test.go | 90 --- core/stat/stat_prepare_slot.go | 4 +- core/stat/stat_slot.go | 42 +- core/system/rule.go | 11 +- core/system/rule_manager.go | 46 +- core/system/rule_manager_test.go | 42 +- core/system/rule_test.go | 6 +- core/system/slot.go | 14 +- core/system/slot_test.go | 26 +- core/system/sys_stat.go | 4 +- example/datasource/nacos/nacos_example.go | 123 ++++ .../concurrency_limitation_example.go | 54 ++ example/qps/qps_limit_example.go | 10 +- example/qps/qps_limit_example_test.go | 10 +- example/warm_up/qps_warm_up_example.go | 16 +- example/warm_up/qps_warm_up_example_test.go | 12 +- ext/datasource/consul/consul.go | 10 +- ext/datasource/consul/consul_test.go | 10 +- ext/datasource/datasource.go | 3 +- ext/datasource/datasource_test.go | 32 + ext/datasource/etcdv3/etcdv3.go | 11 +- ext/datasource/file/refreshable_file.go | 52 +- ext/datasource/file/refreshable_file_test.go | 8 +- ext/datasource/helper.go | 28 +- ext/datasource/helper_test.go | 560 ++++++++++-------- ext/datasource/nacos/nacos.go | 10 +- ext/datasource/nacos/nacos_example.go | 60 -- ext/datasource/nacos/nacos_test.go | 47 +- ext/datasource/property.go | 2 +- ext/datasource/property_test.go | 8 +- go.mod | 9 +- go.sum | 291 ++------- logging/logging.go | 279 ++++----- logging/logging_test.go | 46 +- .../circuitbreaker_slot_integration_test.go | 17 +- tests/testdata/extension/SystemRule.json | 6 +- tests/testdata/extension/SystemRule2.json | 6 +- tests/testdata/extension/SystemRule3.json | 4 +- tests/testdata/extension/helper/FlowRule.json | 30 +- util/auto_recover.go | 2 +- util/file.go | 9 - 122 files changed, 3614 insertions(+), 2015 deletions(-) create mode 100644 .github/workflows/go.yml delete mode 100644 .travis.yml delete mode 100644 adapter/dubbo/consumer_filter.go delete mode 100644 adapter/dubbo/consumer_filter_test.go delete mode 100644 adapter/dubbo/fallback.go delete mode 100644 adapter/dubbo/filter.go delete mode 100644 adapter/dubbo/provider_filter.go delete mode 100644 adapter/dubbo/provider_filter_test.go delete mode 100644 adapter/dubbo/util.go delete mode 100644 adapter/dubbo/util_test.go create mode 100644 adapter/micro/doc.go create mode 100644 api/doc.go create mode 100644 core/circuitbreaker/doc.go create mode 100644 core/circuitbreaker/slot_test.go create mode 100644 core/flow/doc.go create mode 100644 core/flow/slot_test.go create mode 100644 core/flow/standalone_stat_slot.go create mode 100644 core/isolation/doc.go create mode 100644 core/isolation/rule.go create mode 100644 core/isolation/rule_manager.go create mode 100644 core/isolation/rule_manager_test.go create mode 100644 core/isolation/slot.go delete mode 100644 core/stat/resource_node_test.go create mode 100644 example/datasource/nacos/nacos_example.go create mode 100644 example/isolation/concurrency_limitation_example.go delete mode 100644 ext/datasource/nacos/nacos_example.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 000000000..ae2cd741b --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: master + pull_request: + branches: "*" + +jobs: + + build: + name: ${{ matrix.os }} - Go ${{ matrix.go_version }} + runs-on: ubuntu-latest + strategy: + # If you want to matrix build , you can append the following list. + matrix: + go_version: + - 1.13 + - 1.14 + os: + - ubuntu-latest + + steps: + + - name: Set up Go ${{ matrix.go_version }} + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go_version }} + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache build dependence + uses: actions/cache@v2 + with: + # Cache + path: ~/go/pkg/mod + # Cache key + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # An ordered list of keys to use for restoring the cache if no cache hit occurred for key + restore-keys: ${{ runner.os }}-go- + + - name: Install goimports + run: go get golang.org/x/tools/cmd/goimports + + - name: Install go ci lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.27.0 + + - name: Run Linter + run: golangci-lint run --timeout=10m -v --disable-all --enable=govet --enable=staticcheck --enable=ineffassign --enable=misspell + + - name: Test + run: | + diff -u <(echo -n) <(gofmt -d -s .) + diff -u <(echo -n) <(goimports -d .) + go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic + + - name: Coverage + run: bash <(curl -s https://codecov.io/bash) diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 10e3676ac..000000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: go - -dist: xenial -sudo: required - -go: - - 1.13.x - - 1.14.x -env: - - GO111MODULE=on - -before_install: go get golang.org/x/tools/cmd/goimports - -script: - - diff -u <(echo -n) <(gofmt -d -s .) - - diff -u <(echo -n) <(goimports -d .) - - go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 7393424ce..75193f104 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,15 @@ # Sentinel: The Sentinel of Your Microservices +![CI](https://github.com/alibaba/sentinel-golang/workflows/CI/badge.svg?branch=master) [![Build Status](https://travis-ci.org/alibaba/sentinel-golang.svg?branch=master)](https://travis-ci.org/alibaba/sentinel-golang) [![codecov](https://codecov.io/gh/alibaba/sentinel-golang/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/sentinel-golang) [![Go Report Card](https://goreportcard.com/badge/github.com/alibaba/sentinel-golang)](https://goreportcard.com/report/github.com/alibaba/sentinel-golang) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Gitter](https://badges.gitter.im/alibaba/Sentinel.svg)](https://gitter.im/alibaba/Sentinel) +[![GitHub last commit](https://img.shields.io/github/last-commit/alibaba/sentinel-golang.svg?style=flat-square)](https://github.com/alibaba/sentinel-golang/commits/dev) +[![GitHub repo size](https://img.shields.io/github/repo-size/alibaba/sentinel-golang)](https://github.com/alibaba/sentinel-golang) +[![GitHub closed issues](https://img.shields.io/github/issues-closed/alibaba/sentinel-golang.svg?style=flat-square)](alibaba/sentinel-golang/issues?q=is%3Aissue+is%3Aclosed) ## Introduction diff --git a/adapter/dubbo/consumer_filter.go b/adapter/dubbo/consumer_filter.go deleted file mode 100644 index 66f592cc7..000000000 --- a/adapter/dubbo/consumer_filter.go +++ /dev/null @@ -1,57 +0,0 @@ -package dubbo - -import ( - "context" - - sentinel "github.com/alibaba/sentinel-golang/api" - "github.com/alibaba/sentinel-golang/core/base" - "github.com/apache/dubbo-go/protocol" -) - -type consumerFilter struct{} - -func (d *consumerFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { - methodResourceName := getResourceName(invoker, invocation, getConsumerPrefix()) - interfaceResourceName := "" - if getInterfaceGroupAndVersionEnabled() { - interfaceResourceName = getColonSeparatedKey(invoker.GetUrl()) - } else { - interfaceResourceName = invoker.GetUrl().Service() - } - var ( - interfaceEntry *base.SentinelEntry - methodEntry *base.SentinelEntry - b *base.BlockError - ) - - interfaceEntry, b = sentinel.Entry(interfaceResourceName, sentinel.WithResourceType(base.ResTypeRPC), sentinel.WithTrafficType(base.Outbound)) - if b != nil { - // interface blocked - return consumerDubboFallback(ctx, invoker, invocation, b) - } - ctx = context.WithValue(ctx, InterfaceEntryKey, interfaceEntry) - - methodEntry, b = sentinel.Entry(methodResourceName, sentinel.WithResourceType(base.ResTypeRPC), - sentinel.WithTrafficType(base.Outbound), sentinel.WithArgs(invocation.Arguments()...)) - if b != nil { - // method blocked - return consumerDubboFallback(ctx, invoker, invocation, b) - } - ctx = context.WithValue(ctx, MethodEntryKey, methodEntry) - - return invoker.Invoke(ctx, invocation) -} - -func (d *consumerFilter) OnResponse(ctx context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { - if methodEntry := ctx.Value(MethodEntryKey); methodEntry != nil { - e := methodEntry.(*base.SentinelEntry) - sentinel.TraceError(e, result.Error()) - e.Exit() - } - if interfaceEntry := ctx.Value(InterfaceEntryKey); interfaceEntry != nil { - e := interfaceEntry.(*base.SentinelEntry) - sentinel.TraceError(e, result.Error()) - e.Exit() - } - return result -} diff --git a/adapter/dubbo/consumer_filter_test.go b/adapter/dubbo/consumer_filter_test.go deleted file mode 100644 index e305e4008..000000000 --- a/adapter/dubbo/consumer_filter_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package dubbo - -import ( - "context" - "testing" - - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" - "github.com/stretchr/testify/assert" -) - -func TestConsumerFilter_Invoke(t *testing.T) { - f := GetConsumerFilter() - url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") - assert.NoError(t, err) - mockInvoker := protocol.NewBaseInvoker(url) - mockInvocation := invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]string)) - result := f.Invoke(context.TODO(), mockInvoker, mockInvocation) - assert.NoError(t, result.Error()) - // todo more testing code -} diff --git a/adapter/dubbo/fallback.go b/adapter/dubbo/fallback.go deleted file mode 100644 index d3bee5ff0..000000000 --- a/adapter/dubbo/fallback.go +++ /dev/null @@ -1,30 +0,0 @@ -package dubbo - -import ( - "context" - - "github.com/alibaba/sentinel-golang/core/base" - "github.com/apache/dubbo-go/protocol" -) - -var ( - consumerDubboFallback = getDefaultDubboFallback() - providerDubboFallback = getDefaultDubboFallback() -) - -type DubboFallback func(context.Context, protocol.Invoker, protocol.Invocation, *base.BlockError) protocol.Result - -func SetConsumerDubboFallback(f DubboFallback) { - consumerDubboFallback = f -} -func SetProviderDubboFallback(f DubboFallback) { - providerDubboFallback = f -} -func getDefaultDubboFallback() DubboFallback { - return func(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, blockError *base.BlockError) protocol.Result { - result := &protocol.RPCResult{} - result.SetResult(nil) - result.SetError(blockError) - return result - } -} diff --git a/adapter/dubbo/filter.go b/adapter/dubbo/filter.go deleted file mode 100644 index 6579e695c..000000000 --- a/adapter/dubbo/filter.go +++ /dev/null @@ -1,19 +0,0 @@ -package dubbo - -import ( - "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/filter" -) - -func init() { - extension.SetFilter(ProviderFilterName, GetProviderFilter) - extension.SetFilter(ConsumerFilterName, GetConsumerFilter) -} - -func GetConsumerFilter() filter.Filter { - return &consumerFilter{} -} - -func GetProviderFilter() filter.Filter { - return &providerFilter{} -} diff --git a/adapter/dubbo/provider_filter.go b/adapter/dubbo/provider_filter.go deleted file mode 100644 index e5f67768e..000000000 --- a/adapter/dubbo/provider_filter.go +++ /dev/null @@ -1,55 +0,0 @@ -package dubbo - -import ( - "context" - - sentinel "github.com/alibaba/sentinel-golang/api" - "github.com/alibaba/sentinel-golang/core/base" - "github.com/apache/dubbo-go/protocol" -) - -type providerFilter struct{} - -func (d *providerFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { - methodResourceName := getResourceName(invoker, invocation, getProviderPrefix()) - interfaceResourceName := "" - if getInterfaceGroupAndVersionEnabled() { - interfaceResourceName = getColonSeparatedKey(invoker.GetUrl()) - } else { - interfaceResourceName = invoker.GetUrl().Service() - } - var ( - interfaceEntry *base.SentinelEntry - methodEntry *base.SentinelEntry - b *base.BlockError - ) - interfaceEntry, b = sentinel.Entry(interfaceResourceName, sentinel.WithResourceType(base.ResTypeRPC), sentinel.WithTrafficType(base.Inbound)) - if b != nil { - // interface blocked - return providerDubboFallback(ctx, invoker, invocation, b) - } - ctx = context.WithValue(ctx, InterfaceEntryKey, interfaceEntry) - - methodEntry, b = sentinel.Entry(methodResourceName, sentinel.WithResourceType(base.ResTypeRPC), - sentinel.WithTrafficType(base.Inbound), sentinel.WithArgs(invocation.Arguments()...)) - if b != nil { - // method blocked - return providerDubboFallback(ctx, invoker, invocation, b) - } - ctx = context.WithValue(ctx, MethodEntryKey, methodEntry) - return invoker.Invoke(ctx, invocation) -} - -func (d *providerFilter) OnResponse(ctx context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { - if methodEntry := ctx.Value(MethodEntryKey); methodEntry != nil { - e := methodEntry.(*base.SentinelEntry) - sentinel.TraceError(e, result.Error()) - e.Exit() - } - if interfaceEntry := ctx.Value(InterfaceEntryKey); interfaceEntry != nil { - e := interfaceEntry.(*base.SentinelEntry) - sentinel.TraceError(e, result.Error()) - e.Exit() - } - return result -} diff --git a/adapter/dubbo/provider_filter_test.go b/adapter/dubbo/provider_filter_test.go deleted file mode 100644 index 812aafb9a..000000000 --- a/adapter/dubbo/provider_filter_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package dubbo - -import ( - "context" - "testing" - - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" - "github.com/stretchr/testify/assert" -) - -func TestProviderFilter_Invoke(t *testing.T) { - f := GetProviderFilter() - url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") - assert.NoError(t, err) - mockInvoker := protocol.NewBaseInvoker(url) - mockInvocation := invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]string)) - result := f.Invoke(context.TODO(), mockInvoker, mockInvocation) - assert.NoError(t, result.Error()) - // todo more testing code -} diff --git a/adapter/dubbo/util.go b/adapter/dubbo/util.go deleted file mode 100644 index f993065b5..000000000 --- a/adapter/dubbo/util.go +++ /dev/null @@ -1,70 +0,0 @@ -package dubbo - -import ( - "bytes" - "fmt" - - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/protocol" -) - -const ( - ProviderFilterName = "sentinel-provider" - ConsumerFilterName = "sentinel-consumer" - - DefaultProviderPrefix = "dubbo:provider:" - DefaultConsumerPrefix = "dubbo:consumer:" - - MethodEntryKey = "$$sentinelMethodEntry" - InterfaceEntryKey = "$$sentinelInterfaceEntry" -) - -// Currently, a ConcurrentHashMap mechanism is missing. -// All values are filled with default values first. - -func getResourceName(invoker protocol.Invoker, invocation protocol.Invocation, prefix string) string { - var ( - buf bytes.Buffer - interfaceResource string - ) - buf.WriteString(prefix) - if getInterfaceGroupAndVersionEnabled() { - interfaceResource = getColonSeparatedKey(invoker.GetUrl()) - } else { - interfaceResource = invoker.GetUrl().Service() - } - buf.WriteString(interfaceResource) - buf.WriteString(":") - buf.WriteString(invocation.MethodName()) - buf.WriteString("(") - isFirst := true - for _, v := range invocation.ParameterTypes() { - if !isFirst { - buf.WriteString(",") - } - buf.WriteString(v.Name()) - isFirst = false - } - buf.WriteString(")") - return buf.String() -} - -func getConsumerPrefix() string { - return DefaultConsumerPrefix -} - -func getProviderPrefix() string { - return DefaultProviderPrefix -} - -func getInterfaceGroupAndVersionEnabled() bool { - return true -} - -func getColonSeparatedKey(url common.URL) string { - return fmt.Sprintf("%s:%s:%s", - url.Service(), - url.GetParam(constant.GROUP_KEY, ""), - url.GetParam(constant.VERSION_KEY, "")) -} diff --git a/adapter/dubbo/util_test.go b/adapter/dubbo/util_test.go deleted file mode 100644 index 129743948..000000000 --- a/adapter/dubbo/util_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package dubbo - -import ( - "testing" - - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" - "github.com/stretchr/testify/assert" -) - -func TestGetResourceName(t *testing.T) { - url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + - "version=1.0.0&group=myGroup&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") - assert.NoError(t, err) - mockInvoker := protocol.NewBaseInvoker(url) - methodResourceName := getResourceName(mockInvoker, - invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]string)), "prefix_") - assert.Equal(t, "prefix_com.ikurento.user.UserProvider:myGroup:1.0.0:hello()", methodResourceName) -} diff --git a/adapter/echo/middleware_test.go b/adapter/echo/middleware_test.go index 976ef6104..71bf2a3b0 100644 --- a/adapter/echo/middleware_test.go +++ b/adapter/echo/middleware_test.go @@ -18,18 +18,20 @@ func initSentinel(t *testing.T) { t.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "GET:/ping", - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "GET:/ping", + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + StatIntervalInMs: 1000, }, { - Resource: "/api/:uid", - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "/api/:uid", + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + StatIntervalInMs: 1000, }, }) if err != nil { diff --git a/adapter/gin/middleware_test.go b/adapter/gin/middleware_test.go index ca74df795..5ea2fc871 100644 --- a/adapter/gin/middleware_test.go +++ b/adapter/gin/middleware_test.go @@ -18,18 +18,20 @@ func initSentinel(t *testing.T) { t.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "GET:/ping", - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "GET:/ping", + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + StatIntervalInMs: 1000, }, { - Resource: "/api/users/:id", - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "/api/users/:id", + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + StatIntervalInMs: 1000, }, }) if err != nil { diff --git a/adapter/grpc/client_test.go b/adapter/grpc/client_test.go index 319b4edd2..f8b0e6782 100644 --- a/adapter/grpc/client_test.go +++ b/adapter/grpc/client_test.go @@ -23,12 +23,12 @@ func TestUnaryClientIntercept(t *testing.T) { } method := "/grpc.testing.TestService/UnaryCall" t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "client:" + method, - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "client:" + method, + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -41,12 +41,12 @@ func TestUnaryClientIntercept(t *testing.T) { }) t.Run("fail", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "client:" + method, - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "client:" + method, + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -67,12 +67,12 @@ func TestStreamClientIntercept(t *testing.T) { } method := "/grpc.testing.TestService/StreamingOutputCall" t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "client:/grpc.testing.TestService/StreamingOutputCall", - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "client:/grpc.testing.TestService/StreamingOutputCall", + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -87,12 +87,12 @@ func TestStreamClientIntercept(t *testing.T) { }) t.Run("fail", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "client:/grpc.testing.TestService/StreamingOutputCall", - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "client:/grpc.testing.TestService/StreamingOutputCall", + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) diff --git a/adapter/grpc/server_test.go b/adapter/grpc/server_test.go index 68b8d8900..168e8a19f 100644 --- a/adapter/grpc/server_test.go +++ b/adapter/grpc/server_test.go @@ -3,6 +3,7 @@ package grpc import ( "context" "errors" + "os" "testing" sentinel "github.com/alibaba/sentinel-golang/api" @@ -16,6 +17,7 @@ import ( func TestMain(m *testing.M) { _ = sentinel.InitDefault() m.Run() + os.Exit(0) } func TestStreamServerIntercept(t *testing.T) { @@ -29,12 +31,12 @@ func TestStreamServerIntercept(t *testing.T) { } t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "/grpc.testing.TestService/StreamingInputCall", - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "/grpc.testing.TestService/StreamingInputCall", + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -47,12 +49,12 @@ func TestStreamServerIntercept(t *testing.T) { }) t.Run("fail", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "/grpc.testing.TestService/StreamingInputCall", - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "/grpc.testing.TestService/StreamingInputCall", + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -71,12 +73,12 @@ func TestUnaryServerIntercept(t *testing.T) { FullMethod: "/grpc.testing.TestService/UnaryCall", } t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "/grpc.testing.TestService/UnaryCall", - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: "/grpc.testing.TestService/UnaryCall", + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) @@ -99,12 +101,12 @@ func TestUnaryServerIntercept(t *testing.T) { return "abc", nil } t.Run("fail", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "/grpc.testing.TestService/UnaryCall", - MetricType: flow.QPS, - Count: 0, - ControlBehavior: flow.Reject, + Resource: "/grpc.testing.TestService/UnaryCall", + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) diff --git a/adapter/micro/client_test.go b/adapter/micro/client_test.go index e49095ce5..9d5cd76b8 100644 --- a/adapter/micro/client_test.go +++ b/adapter/micro/client_test.go @@ -45,12 +45,12 @@ func TestClientLimiter(t *testing.T) { rsp := &proto.Response{} t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: req.Method(), - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: req.Method(), + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) diff --git a/adapter/micro/doc.go b/adapter/micro/doc.go new file mode 100644 index 000000000..124957364 --- /dev/null +++ b/adapter/micro/doc.go @@ -0,0 +1,21 @@ +/* +This package provides Sentinel integration for go-micro. + +For server side, users may append a Sentinel handler wrapper to go-micro service, like: + + import ( + sentinelPlugin "github.com/alibaba/sentinel-golang/adapter/micro" + ) + + // Append a Sentinel handler wrapper. + micro.NewService(micro.WrapHandler(sentinelPlugin.NewHandlerWrapper())) + +The plugin extracts service method as the resource name by default. +Users may provide customized resource name extractor when creating new +Sentinel handler wrapper (via options). + +Fallback logic: the plugin will return the BlockError by default +if current request is blocked by Sentinel rules. Users may also +provide customized fallback logic via WithXxxBlockFallback(handler) options. +*/ +package micro diff --git a/adapter/micro/server_test.go b/adapter/micro/server_test.go index a6820f5ce..cb3074eec 100644 --- a/adapter/micro/server_test.go +++ b/adapter/micro/server_test.go @@ -55,12 +55,12 @@ func TestServerLimiter(t *testing.T) { log.Fatal(err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: req.Method(), - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: req.Method(), + Threshold: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) @@ -69,12 +69,12 @@ func TestServerLimiter(t *testing.T) { var rsp = &proto.Response{} t.Run("success", func(t *testing.T) { - var _, err = flow.LoadRules([]*flow.FlowRule{ + var _, err = flow.LoadRules([]*flow.Rule{ { - Resource: req.Method(), - MetricType: flow.QPS, - Count: 1, - ControlBehavior: flow.Reject, + Resource: req.Method(), + Threshold: 1, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) assert.Nil(t, err) diff --git a/api/api.go b/api/api.go index 55c8f2fae..1bff3c0de 100644 --- a/api/api.go +++ b/api/api.go @@ -11,7 +11,7 @@ var entryOptsPool = sync.Pool{ return &EntryOptions{ resourceType: base.ResTypeCommon, entryType: base.Outbound, - acquireCount: 1, + batchCount: 1, flag: 0, slotChain: nil, args: nil, @@ -24,7 +24,7 @@ var entryOptsPool = sync.Pool{ type EntryOptions struct { resourceType base.ResourceType entryType base.TrafficType - acquireCount uint32 + batchCount uint32 flag int32 slotChain *base.SlotChain args []interface{} @@ -34,7 +34,7 @@ type EntryOptions struct { func (o *EntryOptions) Reset() { o.resourceType = base.ResTypeCommon o.entryType = base.Outbound - o.acquireCount = 1 + o.batchCount = 1 o.flag = 0 o.slotChain = nil o.args = nil @@ -57,10 +57,18 @@ func WithTrafficType(entryType base.TrafficType) EntryOption { } } +// DEPRECATED: use WithBatchCount instead. // WithAcquireCount sets the resource entry with the given batch count (by default 1). func WithAcquireCount(acquireCount uint32) EntryOption { return func(opts *EntryOptions) { - opts.acquireCount = acquireCount + opts.batchCount = acquireCount + } +} + +// WithBatchCount sets the resource entry with the given batch count (by default 1). +func WithBatchCount(batchCount uint32) EntryOption { + return func(opts *EntryOptions) { + opts.batchCount = batchCount } } @@ -129,7 +137,7 @@ func entry(resource string, options *EntryOptions) (*base.SentinelEntry, *base.B // Get context from pool. ctx := sc.GetPooledContext() ctx.Resource = rw - ctx.Input.AcquireCount = options.acquireCount + ctx.Input.BatchCount = options.batchCount ctx.Input.Flag = options.flag if len(options.args) != 0 { ctx.Input.Args = options.args diff --git a/api/api_test.go b/api/api_test.go index 1ef683fdf..ea0d6e043 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -72,7 +72,7 @@ func Test_entryWithArgsAndChainPass(t *testing.T) { entry, b := entry("abc", &EntryOptions{ resourceType: base.ResTypeCommon, entryType: base.Inbound, - acquireCount: 1, + batchCount: 1, flag: 0, slotChain: sc, }) @@ -112,7 +112,7 @@ func Test_entryWithArgsAndChainBlock(t *testing.T) { entry, b := entry("abc", &EntryOptions{ resourceType: base.ResTypeCommon, entryType: base.Inbound, - acquireCount: 1, + batchCount: 1, flag: 0, slotChain: sc, }) diff --git a/api/doc.go b/api/doc.go new file mode 100644 index 000000000..ef832823e --- /dev/null +++ b/api/doc.go @@ -0,0 +1,50 @@ +// Package api provides the topmost fundamental APIs for users using sentinel-golang. +// Users must initialize Sentinel before loading Sentinel rules. Sentinel support three ways to perform initialization: +// +// 1. api.InitDefault(), using default config to initialize. +// 2. api.InitWithConfig(confEntity *config.Entity), using customized config Entity to initialize. +// 3. api.InitWithConfigFile(configPath string), using yaml file to initialize. +// +// Here is the example code to use Sentinel: +// +// import sentinel "github.com/alibaba/sentinel-golang/api" +// +// err := sentinel.InitDefault() +// if err != nil { +// log.Fatal(err) +// } +// +// //Load sentinel rules +// _, err = flow.LoadRules([]*flow.Rule{ +// { +// Resource: "some-test", +// MetricType: flow.QPS, +// Threshold: 10, +// ControlBehavior: flow.Reject, +// }, +// }) +// if err != nil { +// log.Fatalf("Unexpected error: %+v", err) +// return +// } +// ch := make(chan struct{}) +// for i := 0; i < 10; i++ { +// go func() { +// for { +// e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound)) +// if b != nil { +// // Blocked. We could get the block reason from the BlockError. +// time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) +// } else { +// // Passed, wrap the logic here. +// fmt.Println(util.CurrentTimeMillis(), "passed") +// time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) +// // Be sure the entry is exited finally. +// e.Exit() +// } +// } +// }() +// } +// <-ch +// +package api diff --git a/api/init.go b/api/init.go index 6bfa8138c..262110101 100644 --- a/api/init.go +++ b/api/init.go @@ -9,6 +9,10 @@ import ( "github.com/alibaba/sentinel-golang/util" ) +// Initialization func initialize the Sentinel's runtime environment, including: +// 1. override global config, from manually config or yaml file or env variable +// 2. override global logger +// 3. initiate core component async task, including: metric log, system statistic... // InitDefault initializes Sentinel using the configuration from system // environment and the default value. func InitDefault() error { @@ -47,11 +51,16 @@ func InitWithConfigFile(configPath string) error { // initCoreComponents init core components with default config // it's better SetDefaultConfig before initCoreComponents func initCoreComponents() error { - if err := metric.InitTask(); err != nil { - return err + if config.MetricLogFlushIntervalSec() > 0 { + if err := metric.InitTask(); err != nil { + return err + } + } + + if config.SystemStatCollectIntervalMs() > 0 { + system.InitCollector(config.SystemStatCollectIntervalMs()) } - system.InitCollector(config.SystemStatCollectIntervalMs()) if config.UseCacheTime() { util.StartTimeTicker() } diff --git a/api/slot_chain.go b/api/slot_chain.go index 72045be41..90c9411be 100644 --- a/api/slot_chain.go +++ b/api/slot_chain.go @@ -5,6 +5,7 @@ import ( "github.com/alibaba/sentinel-golang/core/circuitbreaker" "github.com/alibaba/sentinel-golang/core/flow" "github.com/alibaba/sentinel-golang/core/hotspot" + "github.com/alibaba/sentinel-golang/core/isolation" "github.com/alibaba/sentinel-golang/core/log" "github.com/alibaba/sentinel-golang/core/stat" "github.com/alibaba/sentinel-golang/core/system" @@ -27,14 +28,16 @@ func GlobalSlotChain() *base.SlotChain { func BuildDefaultSlotChain() *base.SlotChain { sc := base.NewSlotChain() - sc.AddStatPrepareSlotLast(&stat.StatNodePrepareSlot{}) - sc.AddRuleCheckSlotLast(&system.SystemAdaptiveSlot{}) - sc.AddRuleCheckSlotLast(&flow.FlowSlot{}) + sc.AddStatPrepareSlotLast(&stat.ResourceNodePrepareSlot{}) + sc.AddRuleCheckSlotLast(&system.AdaptiveSlot{}) + sc.AddRuleCheckSlotLast(&flow.Slot{}) + sc.AddRuleCheckSlotLast(&isolation.Slot{}) sc.AddRuleCheckSlotLast(&circuitbreaker.Slot{}) sc.AddRuleCheckSlotLast(&hotspot.Slot{}) - sc.AddStatSlotLast(&stat.StatisticSlot{}) - sc.AddStatSlotLast(&log.LogSlot{}) + sc.AddStatSlotLast(&stat.Slot{}) + sc.AddStatSlotLast(&log.Slot{}) sc.AddStatSlotLast(&circuitbreaker.MetricStatSlot{}) sc.AddStatSlotLast(&hotspot.ConcurrencyStatSlot{}) + sc.AddStatSlotLast(&flow.StandaloneStatSlot{}) return sc } diff --git a/api/tracer.go b/api/tracer.go index 9724be8d5..1a1171f22 100644 --- a/api/tracer.go +++ b/api/tracer.go @@ -3,13 +3,14 @@ package api import ( "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" ) // TraceError records the provided error to the given SentinelEntry. func TraceError(entry *base.SentinelEntry, err error) { defer func() { if e := recover(); e != nil { - logging.Panicf("Failed to TraceError, panic error: %+v", e) + logging.Error(errors.Errorf("%+v", e), "failed to TraceError") return } }() diff --git a/core/base/context.go b/core/base/context.go index 99660e231..be75a859a 100644 --- a/core/base/context.go +++ b/core/base/context.go @@ -67,15 +67,15 @@ func NewEmptyEntryContext() *EntryContext { // The input data of sentinel type SentinelInput struct { - AcquireCount uint32 - Flag int32 - Args []interface{} + BatchCount uint32 + Flag int32 + Args []interface{} // store some values in this context when calling context in slot. Attachments map[interface{}]interface{} } func (i *SentinelInput) reset() { - i.AcquireCount = 1 + i.BatchCount = 1 i.Flag = 0 if len(i.Args) != 0 { i.Args = make([]interface{}, 0) diff --git a/core/base/entry.go b/core/base/entry.go index 88b80ddfd..9967c51da 100644 --- a/core/base/entry.go +++ b/core/base/entry.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" ) type ExitHandler func(entry *SentinelEntry, ctx *EntryContext) error @@ -76,7 +77,7 @@ func (e *SentinelEntry) Exit(exitOps ...ExitOption) { e.exitCtl.Do(func() { defer func() { if err := recover(); err != nil { - logging.Panicf("Sentinel internal panic in entry exit func, err: %+v", err) + logging.Error(errors.Errorf("%+v", err), "Sentinel internal panic in entry exit func") } if e.sc != nil { e.sc.RefurbishContext(ctx) @@ -84,7 +85,7 @@ func (e *SentinelEntry) Exit(exitOps ...ExitOption) { }() for _, handler := range e.exitHandlers { if err := handler(e, ctx); err != nil { - logging.Errorf("Fail to execute exitHandler for resource: %s, err: %+v", e.Resource().Name(), err) + logging.Error(err, "Fail to execute exitHandler", "resource", e.Resource().Name()) } } if e.sc != nil { diff --git a/core/base/result.go b/core/base/result.go index d32ba8795..ce5d1da25 100644 --- a/core/base/result.go +++ b/core/base/result.go @@ -9,6 +9,7 @@ type BlockType uint8 const ( BlockTypeUnknown BlockType = iota BlockTypeFlow + BlockTypeIsolation BlockTypeCircuitBreaking BlockTypeSystemFlow BlockTypeHotSpotParamFlow @@ -20,6 +21,8 @@ func (t BlockType) String() string { return "Unknown" case BlockTypeFlow: return "FlowControl" + case BlockTypeIsolation: + return "BlockTypeIsolation" case BlockTypeCircuitBreaking: return "CircuitBreaking" case BlockTypeSystemFlow: diff --git a/core/base/slot_chain.go b/core/base/slot_chain.go index 4347dbaee..f2cb82b02 100644 --- a/core/base/slot_chain.go +++ b/core/base/slot_chain.go @@ -67,10 +67,10 @@ func NewSlotChain() *SlotChain { ctx.RuleCheckResult = NewTokenResultPass() ctx.Data = make(map[interface{}]interface{}) ctx.Input = &SentinelInput{ - AcquireCount: 1, - Flag: 0, - Args: make([]interface{}, 0), - Attachments: make(map[interface{}]interface{}), + BatchCount: 1, + Flag: 0, + Args: make([]interface{}, 0), + Attachments: make(map[interface{}]interface{}), } return ctx }, @@ -130,7 +130,7 @@ func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult { // If happened, need to add TokenResult in EntryContext defer func() { if err := recover(); err != nil { - logging.Panicf("Sentinel internal panic in SlotChain, err: %+v", err) + logging.Error(errors.Errorf("%+v", err), "Sentinel internal panic in SlotChain") ctx.SetError(errors.Errorf("%+v", err)) return } @@ -186,7 +186,7 @@ func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult { func (sc *SlotChain) exit(ctx *EntryContext) { if ctx == nil || ctx.Entry() == nil { - logging.Errorf("nil ctx or nil associated entry") + logging.Error(errors.New("nil EntryContext or SentinelEntry"), "") return } // The OnCompleted is called only when entry passed diff --git a/core/base/slot_chain_test.go b/core/base/slot_chain_test.go index 645c9362e..4b13d403c 100644 --- a/core/base/slot_chain_test.go +++ b/core/base/slot_chain_test.go @@ -223,10 +223,10 @@ func TestSlotChain_Entry_Pass_And_Exit(t *testing.T) { ctx.SetEntry(NewSentinelEntry(ctx, rw, sc)) ctx.StatNode = &StatNodeMock{} ctx.Input = &SentinelInput{ - AcquireCount: 1, - Flag: 0, - Args: nil, - Attachments: nil, + BatchCount: 1, + Flag: 0, + Args: nil, + Attachments: nil, } ps1 := &prepareSlotMock{} @@ -266,10 +266,10 @@ func TestSlotChain_Entry_Block(t *testing.T) { ctx.Resource = rw ctx.StatNode = &StatNodeMock{} ctx.Input = &SentinelInput{ - AcquireCount: 1, - Flag: 0, - Args: nil, - Attachments: nil, + BatchCount: 1, + Flag: 0, + Args: nil, + Attachments: nil, } rbs := &prepareSlotMock{} @@ -324,10 +324,10 @@ func TestSlotChain_Entry_With_Panic(t *testing.T) { statNodeMock.On("AddErrorRequest", mock.Anything).Return() ctx.StatNode = statNodeMock ctx.Input = &SentinelInput{ - AcquireCount: 1, - Flag: 0, - Args: nil, - Attachments: nil, + BatchCount: 1, + Flag: 0, + Args: nil, + Attachments: nil, } rbs := &badPrepareSlotMock{} diff --git a/core/base/stat.go b/core/base/stat.go index bd9404d8a..2a410ded8 100644 --- a/core/base/stat.go +++ b/core/base/stat.go @@ -1,5 +1,9 @@ package base +import ( + "github.com/pkg/errors" +) + type TimePredicate func(uint64) bool type MetricEvent int8 @@ -31,7 +35,7 @@ type ReadStat interface { } type WriteStat interface { - AddMetric(event MetricEvent, count uint64) + AddCount(event MetricEvent, count int64) } // StatNode holds real-time statistics for resources. @@ -46,4 +50,47 @@ type StatNode interface { DecreaseGoroutineNum() Reset() + + // GenerateReadStat generates the readonly metric statistic based on resource level global statistic + // If parameters, sampleCount and intervalInMs, are not suitable for resource level global statistic, return (nil, error) + GenerateReadStat(sampleCount uint32, intervalInMs uint32) (ReadStat, error) +} + +var ( + IllegalGlobalStatisticParamsError = errors.New("Invalid parameters, sampleCount or interval, for resource's global statistic") + IllegalStatisticParamsError = errors.New("Invalid parameters, sampleCount or interval, for metric statistic") + GlobalStatisticNonReusableError = errors.New("The parameters, sampleCount and interval, mismatch for reusing between resource's global statistic and readonly metric statistic.") +) + +func CheckValidityForStatistic(sampleCount, intervalInMs uint32) error { + if intervalInMs == 0 || sampleCount == 0 || intervalInMs%sampleCount != 0 { + return IllegalStatisticParamsError + } + return nil +} + +// CheckValidityForReuseStatistic check the compliance whether readonly metric statistic can be built based on resource's global statistic +// The parameters, sampleCount and intervalInMs, are the parameters of the metric statistic you want to build +// The parameters, parentSampleCount and parentIntervalInMs, are the parameters of the resource's global statistic +// If compliance passes, return nil, if not returns specific error +func CheckValidityForReuseStatistic(sampleCount, intervalInMs uint32, parentSampleCount, parentIntervalInMs uint32) error { + if intervalInMs == 0 || sampleCount == 0 || intervalInMs%sampleCount != 0 { + return IllegalStatisticParamsError + } + bucketLengthInMs := intervalInMs / sampleCount + + if parentIntervalInMs == 0 || parentSampleCount == 0 || parentIntervalInMs%parentSampleCount != 0 { + return IllegalGlobalStatisticParamsError + } + parentBucketLengthInMs := parentIntervalInMs / parentSampleCount + + //SlidingWindowMetric's intervalInMs is not divisible by BucketLeapArray's intervalInMs + if parentIntervalInMs%intervalInMs != 0 { + return GlobalStatisticNonReusableError + } + // BucketLeapArray's BucketLengthInMs is not divisible by SlidingWindowMetric's BucketLengthInMs + if bucketLengthInMs%parentBucketLengthInMs != 0 { + return GlobalStatisticNonReusableError + } + return nil } diff --git a/core/base/stat_test.go b/core/base/stat_test.go index 3111a39dc..aef75a614 100644 --- a/core/base/stat_test.go +++ b/core/base/stat_test.go @@ -1,12 +1,17 @@ package base -import "github.com/stretchr/testify/mock" +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) type StatNodeMock struct { mock.Mock } -func (m *StatNodeMock) AddMetric(event MetricEvent, count uint64) { +func (m *StatNodeMock) AddCount(event MetricEvent, count int64) { m.Called(event, count) } @@ -64,3 +69,19 @@ func (m *StatNodeMock) Reset() { m.Called() return } + +func (m *StatNodeMock) GenerateReadStat(sampleCount uint32, intervalInMs uint32) (ReadStat, error) { + args := m.Called(sampleCount, intervalInMs) + return args.Get(0).(ReadStat), args.Error(1) +} + +func TestCheckValidityForReuseStatistic(t *testing.T) { + assert.Equal(t, CheckValidityForReuseStatistic(3, 1000, 20, 10000), IllegalStatisticParamsError) + assert.Equal(t, CheckValidityForReuseStatistic(0, 1000, 20, 10000), IllegalStatisticParamsError) + assert.Equal(t, CheckValidityForReuseStatistic(2, 1000, 21, 10000), IllegalGlobalStatisticParamsError) + assert.Equal(t, CheckValidityForReuseStatistic(2, 1000, 0, 10000), IllegalGlobalStatisticParamsError) + assert.Equal(t, CheckValidityForReuseStatistic(2, 8000, 20, 10000), GlobalStatisticNonReusableError) + assert.Equal(t, CheckValidityForReuseStatistic(2, 1000, 10, 10000), GlobalStatisticNonReusableError) + assert.Equal(t, CheckValidityForReuseStatistic(1, 1000, 100, 10000), nil) + assert.Equal(t, CheckValidityForReuseStatistic(2, 1000, 20, 10000), nil) +} diff --git a/core/circuitbreaker/circuit_breaker.go b/core/circuitbreaker/circuit_breaker.go index ca8424a67..976aade0b 100644 --- a/core/circuitbreaker/circuit_breaker.go +++ b/core/circuitbreaker/circuit_breaker.go @@ -8,6 +8,7 @@ import ( sbase "github.com/alibaba/sentinel-golang/core/stat/base" "github.com/alibaba/sentinel-golang/logging" "github.com/alibaba/sentinel-golang/util" + "github.com/pkg/errors" ) // @@ -151,7 +152,7 @@ func (b *circuitBreakerBase) fromOpenToHalfOpen(ctx *base.EntryContext) bool { entry := ctx.Entry() if entry == nil { - logging.Errorf("nil entry when probing, rule: %+v", b.rule) + logging.Error(errors.New("nil entry"), "nil entry when probing", "rule", b.rule) } else { // add hook for entry exit // if the current circuit breaker performs the probe through this entry, but the entry was blocked, @@ -342,21 +343,21 @@ func (s *slowRequestLeapArray) ResetBucketTo(bw *sbase.BucketWrap, startTime uin func (s *slowRequestLeapArray) currentCounter() *slowRequestCounter { curBucket, err := s.data.CurrentBucket(s) if err != nil { - logging.Errorf("Failed to get current bucket, current ts=%d, err: %+v.", util.CurrentTimeMillis(), err) + logging.Error(err, "failed to get current bucket") return nil } if curBucket == nil { - logging.Error("Current bucket is nil") + logging.Error(errors.New("nil current BucketWrap"), "Current bucket is nil") return nil } mb := curBucket.Value.Load() if mb == nil { - logging.Error("Current bucket atomic Value is nil") + logging.Error(errors.New("Current bucket atomic Value is nil"), "") return nil } counter, ok := mb.(*slowRequestCounter) if !ok { - logging.Error("Bucket data type error") + logging.Error(errors.New("Bucket data type error"), "") return nil } return counter @@ -368,12 +369,12 @@ func (s *slowRequestLeapArray) allCounter() []*slowRequestCounter { for _, b := range buckets { mb := b.Value.Load() if mb == nil { - logging.Error("Current bucket atomic Value is nil") + logging.Error(errors.New("Current bucket atomic Value is nil"), "") continue } counter, ok := mb.(*slowRequestCounter) if !ok { - logging.Error("Bucket data type error") + logging.Error(errors.New("Bucket data type error"), "") continue } ret = append(ret, counter) @@ -522,21 +523,21 @@ func (s *errorCounterLeapArray) ResetBucketTo(bw *sbase.BucketWrap, startTime ui func (s *errorCounterLeapArray) currentCounter() *errorCounter { curBucket, err := s.data.CurrentBucket(s) if err != nil { - logging.Errorf("Failed to get current bucket, current ts=%d, err: %+v.", util.CurrentTimeMillis(), err) + logging.Error(err, "Failed to get current bucket") return nil } if curBucket == nil { - logging.Error("Current bucket is nil") + logging.Error(errors.New("Current bucket is nil"), "") return nil } mb := curBucket.Value.Load() if mb == nil { - logging.Error("Current bucket atomic Value is nil") + logging.Error(errors.New("Current bucket atomic Value is nil"), "") return nil } counter, ok := mb.(*errorCounter) if !ok { - logging.Error("Bucket data type error") + logging.Error(errors.New("Bucket data type error"), "") return nil } return counter @@ -548,12 +549,12 @@ func (s *errorCounterLeapArray) allCounter() []*errorCounter { for _, b := range buckets { mb := b.Value.Load() if mb == nil { - logging.Error("Current bucket atomic Value is nil") + logging.Error(errors.New("Current bucket atomic Value is nil"), "") continue } counter, ok := mb.(*errorCounter) if !ok { - logging.Error("Bucket data type error") + logging.Error(errors.New("Bucket data type error"), "") continue } ret = append(ret, counter) diff --git a/core/circuitbreaker/circuit_breaker_test.go b/core/circuitbreaker/circuit_breaker_test.go index 9d4cee83d..0fd879599 100644 --- a/core/circuitbreaker/circuit_breaker_test.go +++ b/core/circuitbreaker/circuit_breaker_test.go @@ -1,9 +1,14 @@ package circuitbreaker import ( + "errors" + "sync/atomic" "testing" "github.com/alibaba/sentinel-golang/core/base" + sbase "github.com/alibaba/sentinel-golang/core/stat/base" + "github.com/alibaba/sentinel-golang/logging" + "github.com/alibaba/sentinel-golang/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -37,6 +42,26 @@ func (m *CircuitBreakerMock) OnRequestComplete(rt uint64, err error) { return } +type StateChangeListenerMock struct { + mock.Mock +} + +func (s *StateChangeListenerMock) OnTransformToClosed(prev State, rule Rule) { + _ = s.Called(prev, rule) + logging.Debug("transform to closed", "strategy", rule.Strategy, "prevState", prev.String()) + return +} + +func (s *StateChangeListenerMock) OnTransformToOpen(prev State, rule Rule, snapshot interface{}) { + _ = s.Called(prev, rule, snapshot) + logging.Debug("transform to open", "strategy", rule.Strategy, "prevState", prev.String(), "snapshot", snapshot) +} + +func (s *StateChangeListenerMock) OnTransformToHalfOpen(prev State, rule Rule) { + _ = s.Called(prev, rule) + logging.Debug("transform to Half-Open", "strategy", rule.Strategy, "prevState", prev.String()) +} + func TestStatus(t *testing.T) { t.Run("get_set", func(t *testing.T) { status := new(State) @@ -56,3 +81,294 @@ func TestStatus(t *testing.T) { assert.True(t, status.casState(HalfOpen, Open)) }) } + +func TestSlowRtCircuitBreaker_TryPass(t *testing.T) { + ClearStateChangeListeners() + stateChangeListenerMock := &StateChangeListenerMock{} + stateChangeListenerMock.On("OnTransformToHalfOpen", Open, mock.Anything).Return() + RegisterStateChangeListeners(stateChangeListenerMock) + t.Run("TryPass_Closed", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: SlowRequestRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + } + b, err := newSlowRtCircuitBreaker(r) + assert.Nil(t, err) + pass := b.TryPass(base.NewEmptyEntryContext()) + assert.True(t, pass) + }) + + t.Run("TryPass_Probe", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: SlowRequestRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + } + b, err := newSlowRtCircuitBreaker(r) + assert.Nil(t, err) + + b.state.set(Open) + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + } + e := base.NewSentinelEntry(ctx, base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), nil) + ctx.SetEntry(e) + pass := b.TryPass(ctx) + assert.True(t, pass) + assert.True(t, b.state.get() == HalfOpen) + }) +} + +func TestSlowRt_OnRequestComplete(t *testing.T) { + ClearStateChangeListeners() + r := &Rule{ + Resource: "abc", + Strategy: SlowRequestRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + } + b, err := newSlowRtCircuitBreaker(r) + assert.Nil(t, err) + t.Run("OnRequestComplete_Less_Than_MinRequestMount", func(t *testing.T) { + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Closed) + }) + t.Run("OnRequestComplete_Probe_Failed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Open) + }) + t.Run("OnRequestComplete_Probe_Succeed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(10, nil) + assert.True(t, b.CurrentState() == Closed) + }) +} + +func TestSlowRt_ResetBucketTo(t *testing.T) { + t.Run("ResetBucketTo", func(t *testing.T) { + wrap := &sbase.BucketWrap{ + BucketStart: 1, + Value: atomic.Value{}, + } + wrap.Value.Store(&slowRequestCounter{ + slowCount: 1, + totalCount: 1, + }) + + la := &slowRequestLeapArray{} + la.ResetBucketTo(wrap, util.CurrentTimeMillis()) + counter := wrap.Value.Load().(*slowRequestCounter) + assert.True(t, counter.totalCount == 0 && counter.slowCount == 0) + }) +} + +func TestErrorRatioCircuitBreaker_TryPass(t *testing.T) { + t.Run("TryPass_Closed", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 0.5, + } + b, err := newErrorRatioCircuitBreaker(r) + assert.Nil(t, err) + pass := b.TryPass(base.NewEmptyEntryContext()) + assert.True(t, pass) + }) + + t.Run("TryPass_Probe", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 0.5, + } + b, err := newErrorRatioCircuitBreaker(r) + assert.Nil(t, err) + + b.state.set(Open) + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + } + e := base.NewSentinelEntry(ctx, base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), nil) + ctx.SetEntry(e) + pass := b.TryPass(ctx) + assert.True(t, pass) + assert.True(t, b.state.get() == HalfOpen) + }) +} + +func TestErrorRatio_OnRequestComplete(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorRatio, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 0.5, + } + b, err := newErrorRatioCircuitBreaker(r) + assert.Nil(t, err) + t.Run("OnRequestComplete_Less_Than_MinRequestAmount", func(t *testing.T) { + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Closed) + }) + t.Run("OnRequestComplete_Probe_Succeed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Closed) + }) + t.Run("OnRequestComplete_Probe_Failed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(0, errors.New("errorRatio")) + assert.True(t, b.CurrentState() == Open) + }) +} + +func TestErrorRatio_ResetBucketTo(t *testing.T) { + t.Run("ResetBucketTo", func(t *testing.T) { + wrap := &sbase.BucketWrap{ + BucketStart: 1, + Value: atomic.Value{}, + } + wrap.Value.Store(&errorCounter{ + errorCount: 1, + totalCount: 1, + }) + + la := &errorCounterLeapArray{} + la.ResetBucketTo(wrap, util.CurrentTimeMillis()) + counter := wrap.Value.Load().(*errorCounter) + assert.True(t, counter.errorCount == 0 && counter.totalCount == 0) + }) +} + +func TestErrorCountCircuitBreaker_TryPass(t *testing.T) { + t.Run("TryPass_Closed", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 1, + } + b, err := newErrorCountCircuitBreaker(r) + assert.Nil(t, err) + pass := b.TryPass(base.NewEmptyEntryContext()) + assert.True(t, pass) + }) + + t.Run("TryPass_Probe", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 1, + } + b, err := newErrorCountCircuitBreaker(r) + assert.Nil(t, err) + + b.state.set(Open) + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + } + e := base.NewSentinelEntry(ctx, base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), nil) + ctx.SetEntry(e) + pass := b.TryPass(ctx) + assert.True(t, pass) + assert.True(t, b.state.get() == HalfOpen) + }) +} + +func TestErrorCount_OnRequestComplete(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 1, + } + b, err := newErrorCountCircuitBreaker(r) + assert.Nil(t, err) + t.Run("OnRequestComplete_Less_Than_MinRequestAmount", func(t *testing.T) { + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Closed) + }) + t.Run("OnRequestComplete_Probe_Succeed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(base.NewEmptyEntryContext().Rt(), nil) + assert.True(t, b.CurrentState() == Closed) + }) + t.Run("OnRequestComplete_Probe_Failed", func(t *testing.T) { + b.state.set(HalfOpen) + b.OnRequestComplete(0, errors.New("errorCount")) + assert.True(t, b.CurrentState() == Open) + }) +} + +func TestFromClosedToOpen(t *testing.T) { + ClearStateChangeListeners() + stateChangeListenerMock := &StateChangeListenerMock{} + stateChangeListenerMock.On("OnTransformToOpen", Closed, mock.Anything, mock.Anything).Return() + RegisterStateChangeListeners(stateChangeListenerMock) + t.Run("FromCloseToOpen", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 1, + } + b, err := newErrorCountCircuitBreaker(r) + assert.Nil(t, err) + statusChanged := b.fromClosedToOpen("") + assert.True(t, statusChanged) + stateChangeListenerMock.MethodCalled("OnTransformToOpen", Closed, mock.Anything, mock.Anything) + }) +} + +func TestFromHalfOpenToOpen(t *testing.T) { + ClearStateChangeListeners() + stateChangeListenerMock := &StateChangeListenerMock{} + stateChangeListenerMock.On("OnTransformToOpen", HalfOpen, mock.Anything, mock.Anything).Return() + RegisterStateChangeListeners(stateChangeListenerMock) + t.Run("FromHalfOpenToOpen", func(t *testing.T) { + r := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + Threshold: 1, + } + b, err := newErrorCountCircuitBreaker(r) + assert.Nil(t, err) + b.state.set(HalfOpen) + statusChanged := b.fromHalfOpenToOpen("") + assert.True(t, statusChanged) + assert.True(t, b.nextRetryTimestampMs > 0) + stateChangeListenerMock.MethodCalled("OnTransformToOpen", HalfOpen, mock.Anything, mock.Anything) + }) +} diff --git a/core/circuitbreaker/doc.go b/core/circuitbreaker/doc.go new file mode 100644 index 000000000..a8e97a603 --- /dev/null +++ b/core/circuitbreaker/doc.go @@ -0,0 +1,113 @@ +// Package circuitbreaker implements the circuit breaker. +// +// Sentinel circuit breaker module converts each Rule into a CircuitBreaker. Each CircuitBreaker has its own statistical structure. +// +// Sentinel circuit breaker module supports three strategies: +// +// 1. SlowRequestRatio: the ratio of slow response time entry(entry's response time is great than max slow response time) exceeds the threshold. The following entry to resource will be broken. +// In SlowRequestRatio strategy, user must set max response time. +// 2. ErrorRatio: the ratio of error entry exceeds the threshold. The following entry to resource will be broken. +// 3. ErrorCount: the number of error entry exceeds the threshold. The following entry to resource will be broken. +// +// Sentinel circuit breaker is implemented based on state machines. There are three state: +// +// 1. Closed: all entries could pass checking. +// 2. Open: the circuit breaker is broken, all entries are blocked. After retry timeout, circuit breaker switches state to Half-Open and allows one entry to probe whether the resource returns to its expected state. +// 3. Half-Open: the circuit breaker is in a temporary state of probing, only one entry is allowed to access resource, others are blocked. +// +// Sentinel circuit breaker provides the listener to listen on the state changes. +// +// type StateChangeListener interface { +// OnTransformToClosed(prev State, rule Rule) +// +// OnTransformToOpen(prev State, rule Rule, snapshot interface{}) +// +// OnTransformToHalfOpen(prev State, rule Rule) +// } +// +// Here is the example code to use circuit breaker: +// +// type stateChangeTestListener struct {} +// +// func (s *stateChangeTestListener) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) { +// fmt.Printf("rule.steategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis()) +// } +// +// func (s *stateChangeTestListener) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) { +// fmt.Printf("rule.steategy: %+v, From %s to Open, snapshot: %.2f, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis()) +// } +// +// func (s *stateChangeTestListener) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) { +// fmt.Printf("rule.steategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis()) +// } +// +// func main() { +// err := sentinel.InitDefault() +// if err != nil { +// log.Fatal(err) +// } +// ch := make(chan struct{}) +// // Register a state change listener so that we could observer the state change of the internal circuit breaker. +// circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{}) +// +// _, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{ +// // Statistic time span=10s, recoveryTimeout=3s, slowRtUpperBound=50ms, maxSlowRequestRatio=50% +// { +// Resource: "abc", +// Strategy: circuitbreaker.SlowRequestRatio, +// RetryTimeoutMs: 3000, +// MinRequestAmount: 10, +// StatIntervalMs: 10000, +// MaxAllowedRtMs: 50, +// Threshold: 0.5, +// }, +// // Statistic time span=10s, recoveryTimeout=3s, maxErrorRatio=50% +// { +// Resource: "abc", +// Strategy: circuitbreaker.ErrorRatio, +// RetryTimeoutMs: 3000, +// MinRequestAmount: 10, +// StatIntervalMs: 10000, +// Threshold: 0.5, +// }, +// }) +// if err != nil { +// log.Fatal(err) +// } +// +// fmt.Println("Sentinel Go circuit breaking demo is running. You may see the pass/block metric in the metric log.") +// go func() { +// for { +// e, b := sentinel.Entry("abc") +// if b != nil { +// //fmt.Println("g1blocked") +// time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) +// } else { +// if rand.Uint64()%20 > 9 { +// // Record current invocation as error. +// sentinel.TraceError(e, errors.New("biz error")) +// } +// //fmt.Println("g1passed") +// time.Sleep(time.Duration(rand.Uint64()%80+10) * time.Millisecond) +// e.Exit() +// } +// } +// }() +// +// go func() { +// for { +// e, b := sentinel.Entry("abc") +// if b != nil { +// //fmt.Println("g2blocked") +// time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) +// } else { +// //fmt.Println("g2passed") +// time.Sleep(time.Duration(rand.Uint64()%80) * time.Millisecond) +// e.Exit() +// } +// } +// }() +// <-ch +// } +// +package circuitbreaker diff --git a/core/circuitbreaker/rule.go b/core/circuitbreaker/rule.go index 1fd5521e5..f9342d372 100644 --- a/core/circuitbreaker/rule.go +++ b/core/circuitbreaker/rule.go @@ -8,7 +8,7 @@ import ( // Strategy represents the strategy of circuit breaker. // Each strategy is associated with one rule type. -type Strategy int8 +type Strategy uint32 const ( // SlowRequestRatio strategy changes the circuit breaker state based on slow request ratio diff --git a/core/circuitbreaker/rule_manager.go b/core/circuitbreaker/rule_manager.go index 00fb04c8a..2e9efc2b4 100644 --- a/core/circuitbreaker/rule_manager.go +++ b/core/circuitbreaker/rule_manager.go @@ -2,7 +2,7 @@ package circuitbreaker import ( "fmt" - "strings" + "reflect" "sync" "github.com/alibaba/sentinel-golang/logging" @@ -32,7 +32,7 @@ func init() { } stat, ok := reuseStat.(*slowRequestLeapArray) if !ok || stat == nil { - logging.Warnf("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*slowRequestLeapArray, in fact: %+v", stat) + logging.Warn("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*slowRequestLeapArray", "statType", reflect.TypeOf(stat).Name()) return newSlowRtCircuitBreaker(r) } return newSlowRtCircuitBreakerWithStat(r, stat), nil @@ -47,7 +47,7 @@ func init() { } stat, ok := reuseStat.(*errorCounterLeapArray) if !ok || stat == nil { - logging.Warnf("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*errorCounterLeapArray, in fact: %+v", stat) + logging.Warn("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*errorCounterLeapArray", "statType", reflect.TypeOf(stat).Name()) return newErrorRatioCircuitBreaker(r) } return newErrorRatioCircuitBreakerWithStat(r, stat), nil @@ -62,19 +62,42 @@ func init() { } stat, ok := reuseStat.(*errorCounterLeapArray) if !ok || stat == nil { - logging.Warnf("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*errorCounterLeapArray, in fact: %+v", stat) + logging.Warn("Expect to generate circuit breaker with reuse statistic, but fail to do type assertion, expect:*errorCounterLeapArray", "statType", reflect.TypeOf(stat).Name()) return newErrorCountCircuitBreaker(r) } return newErrorCountCircuitBreakerWithStat(r, stat), nil } } -func GetResRules(resource string) []*Rule { +// GetRulesOfResource returns specific resource's rules based on copy. +// It doesn't take effect for circuit breaker module if user changes the rule. +// GetRulesOfResource need to compete circuit breaker module's global lock and the high performance losses of copy, +// reduce or do not call GetRulesOfResource frequently if possible +func GetRulesOfResource(resource string) []Rule { updateMux.RLock() - ret, ok := breakerRules[resource] + resRules, ok := breakerRules[resource] updateMux.RUnlock() if !ok { - ret = make([]*Rule, 0) + return nil + } + ret := make([]Rule, 0, len(resRules)) + for _, rule := range resRules { + ret = append(ret, *rule) + } + return ret +} + +// GetRules returns all the rules based on copy. +// It doesn't take effect for circuit breaker module if user changes the rule. +// GetRules need to compete circuit breaker module's global lock and the high performance losses of copy, +// reduce or do not call GetRules if possible +func GetRules() []Rule { + updateMux.RLock() + rules := rulesFrom(breakerRules) + updateMux.RUnlock() + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) } return ret } @@ -97,7 +120,7 @@ func LoadRules(rules []*Rule) (bool, error) { return true, err } -func getResBreakers(resource string) []CircuitBreaker { +func getBreakersOfResource(resource string) []CircuitBreaker { ret := make([]CircuitBreaker, 0) updateMux.RLock() resCBs := breakers[resource] @@ -163,7 +186,7 @@ func onRuleUpdate(rules []*Rule) (err error) { continue } if err := IsValid(rule); err != nil { - logging.Warnf("Ignoring invalid circuit breaking rule when loading new rules, rule: %+v, reason: %s", rule, err.Error()) + logging.Warn("Ignoring invalid circuit breaking rule when loading new rules", "rule", rule, "err", err) continue } @@ -189,7 +212,7 @@ func onRuleUpdate(rules []*Rule) (err error) { if r := recover(); r != nil { return } - logging.Debugf("Updating circuit breaker rule spends %d ns.", util.CurrentTimeNano()-start) + logging.Debug("Time statistics(ns) for updating circuit breaker rule", "timeCost", util.CurrentTimeNano()-start) logRuleUpdate(newBreakerRules) }() @@ -214,7 +237,7 @@ func onRuleUpdate(rules []*Rule) (err error) { generator := cbGenFuncMap[r.Strategy] if generator == nil { - logging.Warnf("Ignoring the rule due to unsupported circuit breaking strategy: %v", r) + logging.Warn("Ignoring the rule due to unsupported circuit breaking strategy", "rule", r) continue } @@ -226,7 +249,7 @@ func onRuleUpdate(rules []*Rule) (err error) { cb, e = generator(r, nil) } if cb == nil || e != nil { - logging.Warnf("Ignoring the rule due to bad generated circuit breaker, r: %s, err: %+v", r.String(), e) + logging.Warn("Ignoring the rule due to bad generated circuit breaker", "rule", r, "err", e) continue } @@ -260,15 +283,13 @@ func rulesFrom(rm map[string][]*Rule) []*Rule { return rules } -func logRuleUpdate(rules map[string][]*Rule) { - sb := strings.Builder{} - sb.WriteString("Circuit breaking rules loaded: [") - - for _, r := range rulesFrom(rules) { - sb.WriteString(r.String() + ",") +func logRuleUpdate(m map[string][]*Rule) { + rs := rulesFrom(m) + if len(rs) == 0 { + logging.Info("[CircuitBreakerRuleManager] Circuit breaking rules were cleared") + } else { + logging.Info("[CircuitBreakerRuleManager] Circuit breaking rules were loaded", "rules", rs) } - sb.WriteString("]") - logging.Info(sb.String()) } // Note: this function is not thread-safe. @@ -292,7 +313,7 @@ func SetCircuitBreakerGenerator(s Strategy, generator CircuitBreakerGenFunc) err if generator == nil { return errors.New("nil generator") } - if s >= SlowRequestRatio && s <= ErrorCount { + if s <= ErrorCount { return errors.New("not allowed to replace the generator for default circuit breaking strategies") } updateMux.Lock() @@ -303,8 +324,8 @@ func SetCircuitBreakerGenerator(s Strategy, generator CircuitBreakerGenFunc) err } func RemoveCircuitBreakerGenerator(s Strategy) error { - if s >= SlowRequestRatio && s <= ErrorCount { - return errors.New("not allowed to replace the generator for default circuit breaking strategies") + if s <= ErrorCount { + return errors.New("not allowed to remove the generator for default circuit breaking strategies") } updateMux.Lock() defer updateMux.Unlock() @@ -317,9 +338,6 @@ func IsValid(r *Rule) error { if len(r.Resource) == 0 { return errors.New("empty resource name") } - if int(r.Strategy) < int(SlowRequestRatio) || int(r.Strategy) > int(ErrorCount) { - return errors.New("invalid Strategy") - } if r.StatIntervalMs <= 0 { return errors.New("invalid StatIntervalMs") } diff --git a/core/circuitbreaker/rule_manager_test.go b/core/circuitbreaker/rule_manager_test.go index 4c8c3febf..07f1a23de 100644 --- a/core/circuitbreaker/rule_manager_test.go +++ b/core/circuitbreaker/rule_manager_test.go @@ -150,6 +150,15 @@ func Test_onUpdateRules(t *testing.T) { breakers = make(map[string][]CircuitBreaker) breakerRules = make(map[string][]*Rule) }) + + t.Run("Test_onUpdateRules_invalid", func(t *testing.T) { + r1 := &Rule{ + Resource: "abc", + } + err := onRuleUpdate([]*Rule{r1}) + assert.Nil(t, err) + assert.True(t, len(GetRules()) == 0) + }) } func Test_onRuleUpdate(t *testing.T) { @@ -232,3 +241,102 @@ func Test_onRuleUpdate(t *testing.T) { assert.True(t, reflect.DeepEqual(newCbs[3].BoundRule(), r7)) }) } + +func TestGeneratorCircuitBreaker(t *testing.T) { + r := &Rule{ + Resource: "abc01", + Strategy: ErrorCount, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + Threshold: 10, + } + t.Run("SlowRequestRatio_Nil_Rule", func(t *testing.T) { + generator := cbGenFuncMap[SlowRequestRatio] + cb, err := generator(nil, nil) + assert.Nil(t, cb) + assert.Error(t, err, "nil rule") + }) + + t.Run("SlowRequestRatio_ReuseStat_Unmatched_Type", func(t *testing.T) { + generator := cbGenFuncMap[SlowRequestRatio] + _, err := generator(r, &errorCounterLeapArray{}) + assert.Nil(t, err) + }) + + t.Run("ErrorRatio_ReuseStat_Unmatched_Type", func(t *testing.T) { + generator := cbGenFuncMap[ErrorRatio] + _, err := generator(r, &slowRequestLeapArray{}) + assert.Nil(t, err) + }) + + t.Run("ErrorCount_ReuseStat_Unmatched_Type", func(t *testing.T) { + generator := cbGenFuncMap[ErrorCount] + _, err := generator(r, &slowRequestLeapArray{}) + assert.Nil(t, err) + }) +} + +func TestGetRules(t *testing.T) { + t.Run("TestGetRules", func(t *testing.T) { + r1 := &Rule{ + Resource: "abc", + Strategy: ErrorCount, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + Threshold: 10, + } + + _, _ = LoadRules([]*Rule{r1}) + rules := GetRules() + assert.True(t, len(rules) == 1 && rules[0].Resource == r1.Resource && rules[0].Strategy == r1.Strategy) + _ = ClearRules() + }) +} + +func TestGetBreakersOfResource(t *testing.T) { + r1 := &Rule{ + Resource: "abc", + Strategy: SlowRequestRatio, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + MaxAllowedRtMs: 20, + Threshold: 0.1, + } + + _, _ = LoadRules([]*Rule{r1}) + + cbs := getBreakersOfResource("abc") + assert.True(t, len(cbs) == 1 && cbs[0].BoundRule() == r1) + _ = ClearRules() +} + +func TestSetCircuitBreakerGenerator(t *testing.T) { + t.Run("TestSetCircuitBreakerGenerator_Normal", func(t *testing.T) { + err := SetCircuitBreakerGenerator(100, func(r *Rule, reuseStat interface{}) (CircuitBreaker, error) { + return newSlowRtCircuitBreakerWithStat(r, nil), nil + }) + assert.Nil(t, err) + }) + + t.Run("TestSetCircuitBreakerGenerator_Err", func(t *testing.T) { + err := SetCircuitBreakerGenerator(SlowRequestRatio, func(r *Rule, reuseStat interface{}) (CircuitBreaker, error) { + return newSlowRtCircuitBreakerWithStat(r, nil), nil + }) + assert.Error(t, err, "not allowed to replace the generator for default circuit breaking strategies") + }) +} + +func TestRemoveCircuitBreakerGenerator(t *testing.T) { + t.Run("TestRemoveCircuitBreakerGenerator_Normal", func(t *testing.T) { + err := RemoveCircuitBreakerGenerator(100) + assert.Nil(t, err) + }) + + t.Run("TestRemoveCircuitBreakerGenerator_Err", func(t *testing.T) { + err := RemoveCircuitBreakerGenerator(SlowRequestRatio) + assert.Error(t, err, "not allowed to remove the generator for default circuit breaking strategies") + }) +} diff --git a/core/circuitbreaker/slot.go b/core/circuitbreaker/slot.go index 2f3bc692d..5b1e78768 100644 --- a/core/circuitbreaker/slot.go +++ b/core/circuitbreaker/slot.go @@ -24,7 +24,7 @@ func (b *Slot) Check(ctx *base.EntryContext) *base.TokenResult { } func checkPass(ctx *base.EntryContext) (bool, *Rule) { - breakers := getResBreakers(ctx.Resource.Name()) + breakers := getBreakersOfResource(ctx.Resource.Name()) for _, breaker := range breakers { passed := breaker.TryPass(ctx) if !passed { diff --git a/core/circuitbreaker/slot_test.go b/core/circuitbreaker/slot_test.go new file mode 100644 index 000000000..2b3b9b917 --- /dev/null +++ b/core/circuitbreaker/slot_test.go @@ -0,0 +1,121 @@ +package circuitbreaker + +import ( + "reflect" + "testing" + + "github.com/alibaba/sentinel-golang/core/base" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func Test_Check(t *testing.T) { + + t.Run("Test_Custom_CircuitBreaker_Strategy_Check", func(t *testing.T) { + rules := []*Rule{ + { + Resource: "abc", + Strategy: 101, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + }, + } + e := SetCircuitBreakerGenerator(101, func(r *Rule, reuseStat interface{}) (CircuitBreaker, error) { + circuitBreakerMock := &CircuitBreakerMock{} + circuitBreakerMock.On("TryPass", mock.Anything).Return(false) + circuitBreakerMock.On("BoundRule", mock.Anything).Return(rules[0]) + return circuitBreakerMock, nil + }) + assert.True(t, e == nil) + + _, err := LoadRules(rules) + assert.Nil(t, err) + assert.True(t, len(getBreakersOfResource("abc")) == 1) + s := &Slot{} + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + RuleCheckResult: base.NewTokenResultPass(), + } + token := s.Check(ctx) + assert.True(t, token.IsBlocked()) + _ = ClearRules() + }) + + t.Run("TestCheck_NoPass_NewTokenResultBlocked", func(t *testing.T) { + rules := []*Rule{ + { + Resource: "abc", + Strategy: 102, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + }, + } + e := SetCircuitBreakerGenerator(102, func(r *Rule, reuseStat interface{}) (CircuitBreaker, error) { + circuitBreakerMock := &CircuitBreakerMock{} + circuitBreakerMock.On("TryPass", mock.Anything).Return(false) + circuitBreakerMock.On("BoundRule", mock.Anything).Return(rules[0]) + return circuitBreakerMock, nil + }) + assert.True(t, e == nil) + + _, err := LoadRules(rules) + assert.Nil(t, err) + assert.True(t, len(getBreakersOfResource("abc")) == 1) + + s := &Slot{} + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + } + token := s.Check(ctx) + assert.True(t, token.IsBlocked()) + _ = ClearRules() + }) + + t.Run("TestCheck_Pass", func(t *testing.T) { + e := SetCircuitBreakerGenerator(100, func(r *Rule, reuseStat interface{}) (CircuitBreaker, error) { + circuitBreakerMock := &CircuitBreakerMock{} + circuitBreakerMock.On("TryPass", mock.Anything).Return(true) + return circuitBreakerMock, nil + }) + assert.True(t, e == nil) + + _, err := LoadRules([]*Rule{ + { + Resource: "abc", + Strategy: 100, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 10000, + MaxAllowedRtMs: 50, + Threshold: 0.5, + }, + }) + assert.Nil(t, err) + assert.True(t, len(getBreakersOfResource("abc")) == 1) + + s := &Slot{} + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound), + RuleCheckResult: base.NewTokenResultPass(), + } + token := s.Check(ctx) + assert.True(t, reflect.DeepEqual(token, ctx.RuleCheckResult)) + _ = ClearRules() + }) + + t.Run("TestCheck_No_Resource", func(t *testing.T) { + s := &Slot{} + ctx := &base.EntryContext{ + Resource: base.NewResourceWrapper("", base.ResTypeCommon, base.Inbound), + RuleCheckResult: base.NewTokenResultPass(), + } + token := s.Check(ctx) + assert.True(t, reflect.DeepEqual(token, ctx.RuleCheckResult)) + }) +} diff --git a/core/circuitbreaker/stat_slot.go b/core/circuitbreaker/stat_slot.go index b397a40d6..75fd2b625 100644 --- a/core/circuitbreaker/stat_slot.go +++ b/core/circuitbreaker/stat_slot.go @@ -23,7 +23,7 @@ func (c *MetricStatSlot) OnCompleted(ctx *base.EntryContext) { res := ctx.Resource.Name() err := ctx.Err() rt := ctx.Rt() - for _, cb := range getResBreakers(res) { + for _, cb := range getBreakersOfResource(res) { cb.OnRequestComplete(rt, err) } } diff --git a/core/config/config.go b/core/config/config.go index 1f36ca4fb..7a96ea781 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -1,10 +1,9 @@ package config import ( - "fmt" "io/ioutil" - "log" "os" + "path/filepath" "strconv" "sync" @@ -51,6 +50,7 @@ func OverrideConfigFromEnvAndInitLog() error { return err } + defer logging.Info("print effective global config", "globalConfig", *globalCfg) // Configured Logger is the highest priority if configLogger := Logger(); configLogger != nil { err = logging.ResetGlobalLogger(configLogger) @@ -59,11 +59,15 @@ func OverrideConfigFromEnvAndInitLog() error { } return nil } - err = initializeLogConfig(LogBaseDir(), LogUsePid()) - if err != nil { + + logDir := LogBaseDir() + if len(logDir) == 0 { + logDir = GetDefaultLogDir() + } + if err := initializeLogConfig(logDir, LogUsePid()); err != nil { return err } - logging.Infof("App name resolved: %s", AppName()) + logging.Info("App name resolved", "appName", AppName()) return nil } @@ -86,7 +90,7 @@ func loadFromYamlFile(filePath string) error { if err != nil { return err } - logging.Infof("Resolving Sentinel config from file: %s", filePath) + logging.Info("Resolving Sentinel config from file", "file", filePath) return checkConfValid(&(globalCfg.Sentinel)) } @@ -134,13 +138,12 @@ func initializeLogConfig(logDir string, usePid bool) (err error) { } func reconfigureRecordLogger(logBaseDir string, withPid bool) error { - logDir := util.AddPathSeparatorIfAbsent(logBaseDir) - filePath := logDir + logging.RecordLogFileName + filePath := filepath.Join(logBaseDir, logging.RecordLogFileName) if withPid { filePath = filePath + ".pid" + strconv.Itoa(os.Getpid()) } - fileLogger, err := logging.NewSimpleFileLogger(filePath, "", log.LstdFlags|log.Lshortfile) + fileLogger, err := logging.NewSimpleFileLogger(filePath) if err != nil { return err } @@ -149,7 +152,7 @@ func reconfigureRecordLogger(logBaseDir string, withPid bool) error { return err } - fmt.Println("INFO: log base directory is: " + logDir) + logging.Info("INFO: log base directory is: " + logBaseDir) return nil } @@ -159,7 +162,7 @@ func GetDefaultLogDir() string { if err != nil { return "" } - return util.AddPathSeparatorIfAbsent(home) + logging.DefaultDirName + return filepath.Join(home, logging.DefaultDirName) } func AppName() string { @@ -202,3 +205,22 @@ func SystemStatCollectIntervalMs() uint32 { func UseCacheTime() bool { return globalCfg.UseCacheTime() } + +func GlobalStatisticIntervalMsTotal() uint32 { + return globalCfg.GlobalStatisticIntervalMsTotal() +} + +func GlobalStatisticSampleCountTotal() uint32 { + return globalCfg.GlobalStatisticSampleCountTotal() +} + +func GlobalStatisticBucketLengthInMs() uint32 { + return globalCfg.GlobalStatisticIntervalMsTotal() / GlobalStatisticSampleCountTotal() +} + +func MetricStatisticIntervalMs() uint32 { + return globalCfg.MetricStatisticIntervalMs() +} +func MetricStatisticSampleCount() uint32 { + return globalCfg.MetricStatisticSampleCount() +} diff --git a/core/config/entity.go b/core/config/entity.go index 70ef4b347..44c24c2dc 100644 --- a/core/config/entity.go +++ b/core/config/entity.go @@ -1,6 +1,10 @@ package config import ( + "encoding/json" + "fmt" + + "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/logging" "github.com/pkg/errors" ) @@ -30,7 +34,7 @@ type SentinelConfig struct { // LogConfig represent the configuration of logging in Sentinel. type LogConfig struct { - // logger indicates that using logger to replace default logging. + // Logger indicates that using logger to replace default logging. Logger logging.Logger // Dir represents the log directory path. Dir string @@ -49,6 +53,15 @@ type MetricLogConfig struct { // StatConfig represents the configuration items of statistics. type StatConfig struct { + // GlobalStatisticSampleCountTotal and GlobalStatisticIntervalMsTotal is the per resource's global default statistic sliding window config + GlobalStatisticSampleCountTotal uint32 `yaml:"globalStatisticSampleCountTotal"` + GlobalStatisticIntervalMsTotal uint32 `yaml:"globalStatisticIntervalMsTotal"` + + // MetricStatisticSampleCount and MetricStatisticIntervalMs is the per resource's default readonly metric statistic + // This default readonly metric statistic must be reusable based on global statistic. + MetricStatisticSampleCount uint32 `yaml:"metricStatisticSampleCount"` + MetricStatisticIntervalMs uint32 `yaml:"metricStatisticIntervalMs"` + System SystemStatConfig `yaml:"system"` } @@ -81,6 +94,10 @@ func NewDefaultConfig() *Entity { }, }, Stat: StatConfig{ + GlobalStatisticSampleCountTotal: base.DefaultSampleCountTotal, + GlobalStatisticIntervalMsTotal: base.DefaultIntervalMsTotal, + MetricStatisticSampleCount: base.DefaultSampleCount, + MetricStatisticIntervalMs: base.DefaultIntervalMs, System: SystemStatConfig{ CollectIntervalMs: DefaultSystemStatCollectIntervalMs, }, @@ -114,12 +131,21 @@ func checkConfValid(conf *SentinelConfig) error { if mc.SingleFileMaxSize <= 0 { return errors.New("Illegal metric log globalCfg: singleFileMaxSize <= 0") } - if conf.Stat.System.CollectIntervalMs == 0 { - return errors.New("Bad system stat globalCfg: collectIntervalMs = 0") + if err := base.CheckValidityForReuseStatistic(conf.Stat.MetricStatisticSampleCount, conf.Stat.MetricStatisticIntervalMs, + conf.Stat.GlobalStatisticSampleCountTotal, conf.Stat.GlobalStatisticIntervalMsTotal); err != nil { + return err } return nil } +func (entity *Entity) String() string { + e, err := json.Marshal(entity) + if err != nil { + return fmt.Sprintf("%+v", *entity) + } + return string(e) +} + func (entity *Entity) AppName() string { return entity.Sentinel.App.Name } @@ -160,3 +186,18 @@ func (entity *Entity) SystemStatCollectIntervalMs() uint32 { func (entity *Entity) UseCacheTime() bool { return entity.Sentinel.UseCacheTime } + +func (entity *Entity) GlobalStatisticIntervalMsTotal() uint32 { + return entity.Sentinel.Stat.GlobalStatisticIntervalMsTotal +} + +func (entity *Entity) GlobalStatisticSampleCountTotal() uint32 { + return entity.Sentinel.Stat.GlobalStatisticSampleCountTotal +} + +func (entity *Entity) MetricStatisticIntervalMs() uint32 { + return entity.Sentinel.Stat.MetricStatisticIntervalMs +} +func (entity *Entity) MetricStatisticSampleCount() uint32 { + return entity.Sentinel.Stat.MetricStatisticSampleCount +} diff --git a/core/flow/doc.go b/core/flow/doc.go new file mode 100644 index 000000000..0fc58681e --- /dev/null +++ b/core/flow/doc.go @@ -0,0 +1,16 @@ +// Package flow implements the flow shaping control. +// +// flow module is based on QPS statistic metric +// +// The TrafficShapingController consists of two part: TrafficShapingCalculator and TrafficShapingChecker +// +// 1. TrafficShapingCalculator calculates the actual traffic shaping token threshold. Currently, Sentinel supports two token calculate strategy: Direct and WarmUp. +// 2. TrafficShapingChecker performs checking logic according to current metrics and the traffic shaping strategy, then yield the token result. Currently, Sentinel supports two control behavior: Reject and Throttling. +// +// Besides, Sentinel supports customized TrafficShapingCalculator and TrafficShapingChecker. User could call function SetTrafficShapingGenerator to register customized TrafficShapingController and call function RemoveTrafficShapingGenerator to unregister TrafficShapingController. +// There are a few notes users need to be aware of: +// +// 1. The function both SetTrafficShapingGenerator and RemoveTrafficShapingGenerator is not thread safe. +// 2. Users can not override the Sentinel supported TrafficShapingController. +// +package flow diff --git a/core/flow/rule.go b/core/flow/rule.go index 6c2e14789..524637e6d 100644 --- a/core/flow/rule.go +++ b/core/flow/rule.go @@ -5,90 +5,123 @@ import ( "fmt" ) -const ( - // LimitOriginDefault represents all origins. - LimitOriginDefault = "default" - // LimitOriginOther represents all origins excluding those configured in other rules. - // For example, if resource "abc" has a rule whose limit origin is "originA", - // the "other" origin will represents all origins excluding "originA". - LimitOriginOther = "other" -) - -// MetricType represents the target metric type. -type MetricType int32 - -const ( - // Concurrency represents concurrency count. - Concurrency MetricType = iota - // QPS represents request count per second. - QPS -) - // RelationStrategy indicates the flow control strategy based on the relation of invocations. type RelationStrategy int32 const ( - // Direct means flow control by current resource directly. - Direct RelationStrategy = iota + // CurrentResource means flow control by current resource directly. + CurrentResource RelationStrategy = iota // AssociatedResource means flow control by the associated resource rather than current resource. AssociatedResource ) -// ControlBehavior indicates the traffic shaping behaviour. -type ControlBehavior int32 +func (s RelationStrategy) String() string { + switch s { + case CurrentResource: + return "CurrentResource" + case AssociatedResource: + return "AssociatedResource" + default: + return "Undefined" + } +} + +type TokenCalculateStrategy int32 const ( - Reject ControlBehavior = iota + Direct TokenCalculateStrategy = iota WarmUp - Throttling - WarmUpThrottling ) -type ClusterThresholdMode uint32 +func (s TokenCalculateStrategy) String() string { + switch s { + case Direct: + return "Direct" + case WarmUp: + return "WarmUp" + default: + return "Undefined" + } +} + +type ControlBehavior int32 const ( - AvgLocalThreshold ClusterThresholdMode = iota - GlobalThreshold + Reject ControlBehavior = iota + Throttling ) -type ClusterRuleConfig struct { - ThresholdType ClusterThresholdMode `json:"thresholdType"` +func (s ControlBehavior) String() string { + switch s { + case Reject: + return "Reject" + case Throttling: + return "Throttling" + default: + return "Undefined" + } } -// FlowRule describes the strategy of flow control. -type FlowRule struct { +// Rule describes the strategy of flow control, the flow control strategy is based on QPS statistic metric +type Rule struct { // ID represents the unique ID of the rule (optional). - ID uint64 `json:"id,omitempty"` - + ID string `json:"id,omitempty"` // Resource represents the resource name. - Resource string `json:"resource"` - // LimitOrigin represents the target origin (reserved field). - LimitOrigin string `json:"limitOrigin"` - MetricType MetricType `json:"metricType"` - // Count represents the threshold. - Count float64 `json:"count"` - RelationStrategy RelationStrategy `json:"relationStrategy"` - ControlBehavior ControlBehavior `json:"controlBehavior"` - - RefResource string `json:"refResource"` - WarmUpPeriodSec uint32 `json:"warmUpPeriodSec"` - MaxQueueingTimeMs uint32 `json:"maxQueueingTimeMs"` - // ClusterMode indicates whether the rule is for cluster flow control or local. - ClusterMode bool `json:"clusterMode"` - ClusterConfig ClusterRuleConfig `json:"clusterConfig"` - WarmUpColdFactor uint32 `json:"warmUpColdFactor"` + Resource string `json:"resource"` + TokenCalculateStrategy TokenCalculateStrategy `json:"tokenCalculateStrategy"` + ControlBehavior ControlBehavior `json:"controlBehavior"` + // Threshold means the threshold during StatIntervalInMs + // If StatIntervalInMs is 1000(1 second), Threshold means QPS + Threshold float64 `json:"threshold"` + RelationStrategy RelationStrategy `json:"relationStrategy"` + RefResource string `json:"refResource"` + MaxQueueingTimeMs uint32 `json:"maxQueueingTimeMs"` + WarmUpPeriodSec uint32 `json:"warmUpPeriodSec"` + WarmUpColdFactor uint32 `json:"warmUpColdFactor"` + // StatIntervalInMs indicates the statistic interval and it's the optional setting for flow Rule. + // If user doesn't set StatIntervalInMs, that means using default metric statistic of resource. + // If the StatIntervalInMs user specifies can not reuse the global statistic of resource, + // sentinel will generate independent statistic structure for this rule. + StatIntervalInMs uint32 `json:"statIntervalInMs"` +} + +func (r *Rule) isEqualsTo(newRule *Rule) bool { + if newRule == nil { + return false + } + if !(r.Resource == newRule.Resource && r.RelationStrategy == newRule.RelationStrategy && + r.RefResource == newRule.RefResource && r.StatIntervalInMs == newRule.StatIntervalInMs && + r.TokenCalculateStrategy == newRule.TokenCalculateStrategy && r.ControlBehavior == newRule.ControlBehavior && r.Threshold == newRule.Threshold && + r.MaxQueueingTimeMs == newRule.MaxQueueingTimeMs && r.WarmUpPeriodSec == newRule.WarmUpPeriodSec && r.WarmUpColdFactor == newRule.WarmUpColdFactor) { + return false + } + return true +} + +func (r *Rule) isStatReusable(newRule *Rule) bool { + if newRule == nil { + return false + } + return r.Resource == newRule.Resource && r.RelationStrategy == newRule.RelationStrategy && + r.RefResource == newRule.RefResource && r.StatIntervalInMs == newRule.StatIntervalInMs +} + +func (r *Rule) needStatistic() bool { + return !(r.TokenCalculateStrategy == Direct && r.ControlBehavior == Throttling) } -func (f *FlowRule) String() string { - b, err := json.Marshal(f) +func (r *Rule) String() string { + b, err := json.Marshal(r) if err != nil { // Return the fallback string - return fmt.Sprintf("FlowRule{resource=%s, id=%d, metricType=%d, threshold=%.2f}", - f.Resource, f.ID, f.MetricType, f.Count) + return fmt.Sprintf("Rule{Resource=%s, TokenCalculateStrategy=%s, ControlBehavior=%s, "+ + "Threshold=%.2f, RelationStrategy=%s, RefResource=%s, MaxQueueingTimeMs=%d, WarmUpPeriodSec=%d, WarmUpColdFactor=%d, StatIntervalInMs=%d}", + r.Resource, r.TokenCalculateStrategy, r.ControlBehavior, r.Threshold, r.RelationStrategy, r.RefResource, + r.MaxQueueingTimeMs, r.WarmUpPeriodSec, r.WarmUpColdFactor, r.StatIntervalInMs) } return string(b) } -func (f *FlowRule) ResourceName() string { - return f.Resource +func (r *Rule) ResourceName() string { + return r.Resource } diff --git a/core/flow/rule_manager.go b/core/flow/rule_manager.go index ec73706af..c62280af9 100644 --- a/core/flow/rule_manager.go +++ b/core/flow/rule_manager.go @@ -1,50 +1,127 @@ package flow import ( - "encoding/json" "fmt" + "reflect" "sync" + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/core/config" + "github.com/alibaba/sentinel-golang/core/stat" + sbase "github.com/alibaba/sentinel-golang/core/stat/base" "github.com/alibaba/sentinel-golang/logging" "github.com/alibaba/sentinel-golang/util" "github.com/pkg/errors" ) // TrafficControllerGenFunc represents the TrafficShapingController generator function of a specific control behavior. -type TrafficControllerGenFunc func(*FlowRule) *TrafficShapingController +type TrafficControllerGenFunc func(*Rule, *standaloneStatistic) (*TrafficShapingController, error) + +type trafficControllerGenKey struct { + tokenCalculateStrategy TokenCalculateStrategy + controlBehavior ControlBehavior +} // TrafficControllerMap represents the map storage for TrafficShapingController. type TrafficControllerMap map[string][]*TrafficShapingController var ( - tcGenFuncMap = make(map[ControlBehavior]TrafficControllerGenFunc) + tcGenFuncMap = make(map[trafficControllerGenKey]TrafficControllerGenFunc) tcMap = make(TrafficControllerMap) tcMux = new(sync.RWMutex) + currentRules = make([]*Rule, 0) ) func init() { // Initialize the traffic shaping controller generator map for existing control behaviors. - tcGenFuncMap[Reject] = func(rule *FlowRule) *TrafficShapingController { - return NewTrafficShapingController(NewDefaultTrafficShapingCalculator(rule.Count), NewDefaultTrafficShapingChecker(rule), rule) + tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: Direct, + controlBehavior: Reject, + }] = func(rule *Rule, boundStat *standaloneStatistic) (*TrafficShapingController, error) { + if boundStat == nil { + var err error + boundStat, err = generateStatFor(rule) + if err != nil { + return nil, err + } + } + tsc, err := NewTrafficShapingController(rule, boundStat) + if err != nil || tsc == nil { + return nil, err + } + tsc.flowCalculator = NewDirectTrafficShapingCalculator(tsc, rule.Threshold) + tsc.flowChecker = NewRejectTrafficShapingChecker(tsc, rule) + return tsc, nil } - tcGenFuncMap[Throttling] = func(rule *FlowRule) *TrafficShapingController { - return NewTrafficShapingController(NewDefaultTrafficShapingCalculator(rule.Count), NewThrottlingChecker(rule.MaxQueueingTimeMs), rule) + tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: Direct, + controlBehavior: Throttling, + }] = func(rule *Rule, boundStat *standaloneStatistic) (*TrafficShapingController, error) { + if boundStat == nil { + var err error + boundStat, err = generateStatFor(rule) + if err != nil { + return nil, err + } + } + tsc, err := NewTrafficShapingController(rule, boundStat) + if err != nil || tsc == nil { + return nil, err + } + tsc.flowCalculator = NewDirectTrafficShapingCalculator(tsc, rule.Threshold) + tsc.flowChecker = NewThrottlingChecker(tsc, rule.MaxQueueingTimeMs) + return tsc, nil } - tcGenFuncMap[WarmUp] = func(rule *FlowRule) *TrafficShapingController { - return NewTrafficShapingController(NewWarmUpTrafficShapingCalculator(rule), NewDefaultTrafficShapingChecker(rule), rule) + tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: WarmUp, + controlBehavior: Reject, + }] = func(rule *Rule, boundStat *standaloneStatistic) (*TrafficShapingController, error) { + if boundStat == nil { + var err error + boundStat, err = generateStatFor(rule) + if err != nil { + return nil, err + } + } + tsc, err := NewTrafficShapingController(rule, boundStat) + if err != nil || tsc == nil { + return nil, err + } + tsc.flowCalculator = NewWarmUpTrafficShapingCalculator(tsc, rule) + tsc.flowChecker = NewRejectTrafficShapingChecker(tsc, rule) + return tsc, nil + } + tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: WarmUp, + controlBehavior: Throttling, + }] = func(rule *Rule, boundStat *standaloneStatistic) (*TrafficShapingController, error) { + if boundStat == nil { + var err error + boundStat, err = generateStatFor(rule) + if err != nil { + return nil, err + } + } + tsc, err := NewTrafficShapingController(rule, boundStat) + if err != nil || tsc == nil { + return nil, err + } + tsc.flowCalculator = NewWarmUpTrafficShapingCalculator(tsc, rule) + tsc.flowChecker = NewThrottlingChecker(tsc, rule.MaxQueueingTimeMs) + return tsc, nil } } func logRuleUpdate(m TrafficControllerMap) { - bs, err := json.Marshal(rulesFrom(m)) - if err != nil { - logging.Info("[FlowRuleManager] Flow rules loaded") + rs := rulesFrom(m) + if len(rs) == 0 { + logging.Info("[FlowRuleManager] Flow rules were cleared") } else { - logging.Infof("[FlowRuleManager] Flow rules loaded: %s", bs) + logging.Info("[FlowRuleManager] Flow rules were loaded", "rules", rs) } } -func onRuleUpdate(rules []*FlowRule) (err error) { +func onRuleUpdate(rules []*Rule) (err error) { defer func() { if r := recover(); r != nil { var ok bool @@ -55,7 +132,19 @@ func onRuleUpdate(rules []*FlowRule) (err error) { } }() - m := buildFlowMap(rules) + resRulesMap := make(map[string][]*Rule) + for _, rule := range rules { + if err := IsValidRule(rule); err != nil { + logging.Warn("ignoring invalid flow rule", "rule", rule, "reason", err) + continue + } + resRules, exist := resRulesMap[rule.Resource] + if !exist { + resRules = make([]*Rule, 0, 1) + } + resRulesMap[rule.Resource] = append(resRules, rule) + } + m := make(TrafficControllerMap, len(resRulesMap)) start := util.CurrentTimeNano() tcMux.Lock() @@ -64,35 +153,84 @@ func onRuleUpdate(rules []*FlowRule) (err error) { if r := recover(); r != nil { return } - logging.Debugf("Updating flow rule spends %d ns.", util.CurrentTimeNano()-start) + logging.Debug("time statistic(ns) for updating flow rule", "timeCost", util.CurrentTimeNano()-start) logRuleUpdate(m) }() - + for res, rulesOfRes := range resRulesMap { + m[res] = buildRulesOfRes(res, rulesOfRes) + } tcMap = m + currentRules = rules return nil } // LoadRules loads the given flow rules to the rule manager, while all previous rules will be replaced. -func LoadRules(rules []*FlowRule) (bool, error) { +func LoadRules(rules []*Rule) (bool, error) { // TODO: rethink the design + //check the current rules is the same as the rules to be loaded + if isEqual := reflect.DeepEqual(currentRules, rules); isEqual { + return false, nil + } err := onRuleUpdate(rules) return true, err } -func GetRules() []*FlowRule { +// getRules returns all the rules。Any changes of rules take effect for flow module +// getRules is an internal interface. +func getRules() []*Rule { tcMux.RLock() defer tcMux.RUnlock() return rulesFrom(tcMap) } +// getRulesOfResource returns specific resource's rules。Any changes of rules take effect for flow module +// getRulesOfResource is an internal interface. +func getRulesOfResource(res string) []*Rule { + tcMux.RLock() + defer tcMux.RUnlock() + + resTcs, exist := tcMap[res] + if !exist { + return nil + } + ret := make([]*Rule, 0, len(resTcs)) + for _, tc := range resTcs { + ret = append(ret, tc.BoundRule()) + } + return ret +} + +// GetRules returns all the rules based on copy. +// It doesn't take effect for flow module if user changes the rule. +func GetRules() []Rule { + rules := getRules() + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) + } + return ret +} + +// GetRulesOfResource returns specific resource's rules based on copy. +// It doesn't take effect for flow module if user changes the rule. +func GetRulesOfResource(res string) []Rule { + rules := getRulesOfResource(res) + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) + } + return ret +} + +// ClearRules clears all the rules in flow module. func ClearRules() error { _, err := LoadRules(nil) return err } -func rulesFrom(m TrafficControllerMap) []*FlowRule { - rules := make([]*FlowRule, 0) +func rulesFrom(m TrafficControllerMap) []*Rule { + rules := make([]*Rule, 0) if len(m) == 0 { return rules } @@ -101,38 +239,111 @@ func rulesFrom(m TrafficControllerMap) []*FlowRule { continue } for _, r := range rs { - if r != nil && r.Rule() != nil { - rules = append(rules, r.Rule()) + if r != nil && r.BoundRule() != nil { + rules = append(rules, r.BoundRule()) } } } return rules } -// SetTrafficShapingGenerator sets the traffic controller generator for the given control behavior. -// Note that modifying the generator of default control behaviors is not allowed. -func SetTrafficShapingGenerator(cb ControlBehavior, generator TrafficControllerGenFunc) error { +func generateStatFor(rule *Rule) (*standaloneStatistic, error) { + intervalInMs := rule.StatIntervalInMs + + var retStat standaloneStatistic + + var resNode *stat.ResourceNode + if rule.RelationStrategy == AssociatedResource { + // use associated statistic + resNode = stat.GetOrCreateResourceNode(rule.RefResource, base.ResTypeCommon) + } else { + resNode = stat.GetOrCreateResourceNode(rule.Resource, base.ResTypeCommon) + } + if intervalInMs == 0 || intervalInMs == config.MetricStatisticIntervalMs() { + // default case, use the resource's default statistic + readStat := resNode.DefaultMetric() + retStat.reuseResourceStat = true + retStat.readOnlyMetric = readStat + retStat.writeOnlyMetric = nil + return &retStat, nil + } + + sampleCount := uint32(0) + //calculate the sample count + if intervalInMs > config.GlobalStatisticIntervalMsTotal() { + sampleCount = 1 + } else if intervalInMs < config.GlobalStatisticBucketLengthInMs() { + sampleCount = 1 + } else { + if intervalInMs%config.GlobalStatisticBucketLengthInMs() == 0 { + sampleCount = intervalInMs / config.GlobalStatisticBucketLengthInMs() + } else { + sampleCount = 1 + } + } + err := base.CheckValidityForReuseStatistic(sampleCount, intervalInMs, config.GlobalStatisticSampleCountTotal(), config.GlobalStatisticIntervalMsTotal()) + if err == nil { + // global statistic reusable + readStat, e := resNode.GenerateReadStat(sampleCount, intervalInMs) + if e != nil { + return nil, e + } + retStat.reuseResourceStat = true + retStat.readOnlyMetric = readStat + retStat.writeOnlyMetric = nil + return &retStat, nil + } else if err == base.GlobalStatisticNonReusableError { + logging.Info("flow rule couldn't reuse global statistic and will generate independent statistic", "rule", rule) + retStat.reuseResourceStat = false + realLeapArray := sbase.NewBucketLeapArray(sampleCount, intervalInMs) + metricStat, e := sbase.NewSlidingWindowMetric(sampleCount, intervalInMs, realLeapArray) + if e != nil { + return nil, errors.Errorf("fail to generate statistic for warm up rule: %+v, err: %+v", rule, e) + } + retStat.readOnlyMetric = metricStat + retStat.writeOnlyMetric = realLeapArray + return &retStat, nil + } + return nil, errors.Wrapf(err, "fail to new standalone statistic because of invalid StatIntervalInMs in flow.Rule, StatIntervalInMs: %d", intervalInMs) +} + +// SetTrafficShapingGenerator sets the traffic controller generator for the given TokenCalculateStrategy and ControlBehavior. +// Note that modifying the generator of default control strategy is not allowed. +func SetTrafficShapingGenerator(tokenCalculateStrategy TokenCalculateStrategy, controlBehavior ControlBehavior, generator TrafficControllerGenFunc) error { if generator == nil { return errors.New("nil generator") } - if cb >= Reject && cb <= WarmUpThrottling { - return errors.New("not allowed to replace the generator for default control behaviors") + + if tokenCalculateStrategy >= Direct && tokenCalculateStrategy <= WarmUp { + return errors.New("not allowed to replace the generator for default control strategy") + } + if controlBehavior >= Reject && controlBehavior <= Throttling { + return errors.New("not allowed to replace the generator for default control strategy") } tcMux.Lock() defer tcMux.Unlock() - tcGenFuncMap[cb] = generator + tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: tokenCalculateStrategy, + controlBehavior: controlBehavior, + }] = generator return nil } -func RemoveTrafficShapingGenerator(cb ControlBehavior) error { - if cb >= Reject && cb <= WarmUpThrottling { - return errors.New("not allowed to replace the generator for default control behaviors") +func RemoveTrafficShapingGenerator(tokenCalculateStrategy TokenCalculateStrategy, controlBehavior ControlBehavior) error { + if tokenCalculateStrategy >= Direct && tokenCalculateStrategy <= WarmUp { + return errors.New("not allowed to replace the generator for default control strategy") + } + if controlBehavior >= Reject && controlBehavior <= Throttling { + return errors.New("not allowed to replace the generator for default control strategy") } tcMux.Lock() defer tcMux.Unlock() - delete(tcGenFuncMap, cb) + delete(tcGenFuncMap, trafficControllerGenKey{ + tokenCalculateStrategy: tokenCalculateStrategy, + controlBehavior: controlBehavior, + }) return nil } @@ -143,96 +354,123 @@ func getTrafficControllerListFor(name string) []*TrafficShapingController { return tcMap[name] } -// NotThreadSafe (should be guarded by the lock) -func buildFlowMap(rules []*FlowRule) TrafficControllerMap { - m := make(TrafficControllerMap) - if len(rules) == 0 { - return m - } +func calculateReuseIndexFor(r *Rule, oldResCbs []*TrafficShapingController) (equalIdx, reuseStatIdx int) { + // the index of equivalent rule in old circuit breaker slice + equalIdx = -1 + // the index of statistic reusable rule in old circuit breaker slice + reuseStatIdx = -1 - for _, rule := range rules { - if err := IsValidFlowRule(rule); err != nil { - logging.Warnf("Ignoring invalid flow rule: %v, reason: %s", rule, err.Error()) + for idx, oldTc := range oldResCbs { + oldRule := oldTc.BoundRule() + if oldRule.isEqualsTo(r) { + // break if there is equivalent rule + equalIdx = idx + break + } + // search the index of first stat reusable rule + if !oldRule.isStatReusable(r) { continue } - if rule.LimitOrigin == "" { - rule.LimitOrigin = LimitOriginDefault + if reuseStatIdx >= 0 { + // had find reuse rule. + continue } - generator, supported := tcGenFuncMap[rule.ControlBehavior] - if !supported { - logging.Warnf("Ignoring the rule due to unsupported control behavior: %v", rule) + reuseStatIdx = idx + } + return equalIdx, reuseStatIdx +} + +// buildRulesOfRes builds TrafficShapingController slice from rules. the resource of rules must be equals to res +func buildRulesOfRes(res string, rulesOfRes []*Rule) []*TrafficShapingController { + newTcsOfRes := make([]*TrafficShapingController, 0, len(rulesOfRes)) + emptyTcs := make([]*TrafficShapingController, 0, 0) + for _, rule := range rulesOfRes { + if res != rule.Resource { + logging.Error(errors.Errorf("unmatched resource name, expect: %s, actual: %s", res, rule.Resource), "FlowManager: unmatched resource name ", "rule", rule) continue } - tsc := generator(rule) - if tsc == nil { - logging.Warnf("Ignoring the rule due to bad generated traffic controller: %v", rule) + oldResTcs, exist := tcMap[res] + if !exist { + oldResTcs = emptyTcs + } + + equalIdx, reuseStatIdx := calculateReuseIndexFor(rule, oldResTcs) + + // First check equals scenario + if equalIdx >= 0 { + // reuse the old cb + equalOldTc := oldResTcs[equalIdx] + newTcsOfRes = append(newTcsOfRes, equalOldTc) + // remove old cb from oldResCbs + tcMap[res] = append(oldResTcs[:equalIdx], oldResTcs[equalIdx+1:]...) continue } - rulesOfRes, exists := m[rule.Resource] - if !exists { - m[rule.Resource] = []*TrafficShapingController{tsc} + generator, supported := tcGenFuncMap[trafficControllerGenKey{ + tokenCalculateStrategy: rule.TokenCalculateStrategy, + controlBehavior: rule.ControlBehavior, + }] + if !supported || generator == nil { + logging.Error(errors.New("unsupported flow control strategy"), "ignoring the rule due to unsupported control behavior", "rule", rule) + continue + } + var tc *TrafficShapingController + var e error + if reuseStatIdx >= 0 { + tc, e = generator(rule, &(oldResTcs[reuseStatIdx].boundStat)) } else { - m[rule.Resource] = append(rulesOfRes, tsc) + tc, e = generator(rule, nil) } + + if tc == nil || e != nil { + logging.Error(errors.New("bad generated traffic controller"), "ignoring the rule due to bad generated traffic controller.", "rule", rule) + continue + } + if reuseStatIdx >= 0 { + // remove old cb from oldResCbs + tcMap[res] = append(oldResTcs[:reuseStatIdx], oldResTcs[reuseStatIdx+1:]...) + } + newTcsOfRes = append(newTcsOfRes, tc) } - return m + return newTcsOfRes } -// IsValidFlowRule checks whether the given FlowRule is valid. -func IsValidFlowRule(rule *FlowRule) error { +// IsValidRule checks whether the given Rule is valid. +func IsValidRule(rule *Rule) error { if rule == nil { - return errors.New("nil FlowRule") + return errors.New("nil Rule") } if rule.Resource == "" { return errors.New("empty resource name") } - if rule.Count < 0 { + if rule.Threshold < 0 { return errors.New("negative threshold") } - if rule.MetricType < 0 { - return errors.New("invalid metric type") - } - if rule.RelationStrategy < 0 { - return errors.New("invalid relation strategy") + if int32(rule.TokenCalculateStrategy) < 0 { + return errors.New("invalid token calculate strategy") } - if rule.ControlBehavior < 0 { + if int32(rule.ControlBehavior) < 0 { return errors.New("invalid control behavior") } - - if rule.RelationStrategy == AssociatedResource && rule.RefResource == "" { - return errors.New("Bad flow rule: invalid control behavior") - } - if err := checkClusterField(rule); err != nil { - return err + if !(rule.RelationStrategy >= CurrentResource && rule.RelationStrategy <= AssociatedResource) { + return errors.New("invalid relation strategy") } - - return checkControlBehaviorField(rule) -} - -func checkClusterField(rule *FlowRule) error { - if rule.ClusterMode && rule.ID <= 0 { - return errors.New("invalid cluster rule ID") + if rule.RelationStrategy == AssociatedResource && rule.RefResource == "" { + return errors.New("Bad flow rule: invalid relation strategy") } - return nil -} - -func checkControlBehaviorField(rule *FlowRule) error { - switch rule.ControlBehavior { - case WarmUp: + if rule.TokenCalculateStrategy == WarmUp { if rule.WarmUpPeriodSec <= 0 { - return errors.New("invalid warmUpPeriodSec") + return errors.New("invalid WarmUpPeriodSec") } if rule.WarmUpColdFactor == 1 { return errors.New("WarmUpColdFactor must be great than 1") } - return nil - case WarmUpThrottling: - if rule.WarmUpPeriodSec <= 0 { - return errors.New("invalid warmUpPeriodSec") - } - return nil - default: + } + if rule.ControlBehavior == Throttling && rule.MaxQueueingTimeMs == 0 { + return errors.New("invalid MaxQueueingTimeMs") + } + if rule.StatIntervalInMs > config.GlobalStatisticIntervalMsTotal()*60 { + return errors.New("StatIntervalInMs must be less than 10 minutes") } return nil } diff --git a/core/flow/rule_manager_test.go b/core/flow/rule_manager_test.go index c841a1571..a85bb2126 100644 --- a/core/flow/rule_manager_test.go +++ b/core/flow/rule_manager_test.go @@ -1,59 +1,399 @@ package flow import ( + "reflect" "testing" + "github.com/alibaba/sentinel-golang/core/stat" + sbase "github.com/alibaba/sentinel-golang/core/stat/base" "github.com/stretchr/testify/assert" ) func TestSetAndRemoveTrafficShapingGenerator(t *testing.T) { - tsc := NewTrafficShapingController(nil, nil, nil) + tsc := &TrafficShapingController{} - err := SetTrafficShapingGenerator(Reject, func(_ *FlowRule) *TrafficShapingController { - return tsc + err := SetTrafficShapingGenerator(Direct, Reject, func(_ *Rule, _ *standaloneStatistic) (*TrafficShapingController, error) { + return tsc, nil }) assert.Error(t, err, "default control behaviors are not allowed to be modified") - err = RemoveTrafficShapingGenerator(Reject) + err = RemoveTrafficShapingGenerator(Direct, Reject) assert.Error(t, err, "default control behaviors are not allowed to be removed") - cb := ControlBehavior(9999) - err = SetTrafficShapingGenerator(cb, func(_ *FlowRule) *TrafficShapingController { - return tsc + err = SetTrafficShapingGenerator(TokenCalculateStrategy(111), ControlBehavior(112), func(_ *Rule, _ *standaloneStatistic) (*TrafficShapingController, error) { + return tsc, nil }) assert.NoError(t, err) resource := "test-customized-tc" - _, err = LoadRules([]*FlowRule{ + _, err = LoadRules([]*Rule{ { - ID: 10, - Count: 20, - MetricType: QPS, - Resource: resource, - ControlBehavior: cb, + Threshold: 20, + Resource: resource, + TokenCalculateStrategy: TokenCalculateStrategy(111), + ControlBehavior: ControlBehavior(112), }, }) + + cs := trafficControllerGenKey{ + tokenCalculateStrategy: TokenCalculateStrategy(111), + controlBehavior: ControlBehavior(112), + } assert.NoError(t, err) - assert.Contains(t, tcGenFuncMap, cb) + assert.Contains(t, tcGenFuncMap, cs) assert.NotZero(t, len(tcMap[resource])) assert.Equal(t, tsc, tcMap[resource][0]) - err = RemoveTrafficShapingGenerator(cb) + err = RemoveTrafficShapingGenerator(TokenCalculateStrategy(111), ControlBehavior(112)) assert.NoError(t, err) - assert.NotContains(t, tcGenFuncMap, cb) + assert.NotContains(t, tcGenFuncMap, cs) - _, _ = LoadRules([]*FlowRule{}) + _, _ = LoadRules([]*Rule{}) } func TestIsValidFlowRule(t *testing.T) { - badRule1 := &FlowRule{ID: 1, Count: 1, MetricType: QPS, Resource: ""} - badRule2 := &FlowRule{ID: 1, Count: -1.9, MetricType: QPS, Resource: "test"} - badRule3 := &FlowRule{Count: 5, MetricType: QPS, Resource: "test", ClusterMode: true} - badRule4 := &FlowRule{Count: 5, MetricType: QPS, Resource: "test", ControlBehavior: WarmUp} - goodRule1 := &FlowRule{Count: 10, MetricType: QPS, Resource: "test", ControlBehavior: Throttling} - - assert.Error(t, IsValidFlowRule(badRule1)) - assert.Error(t, IsValidFlowRule(badRule2)) - assert.Error(t, IsValidFlowRule(badRule3)) - assert.Error(t, IsValidFlowRule(badRule4)) - assert.NoError(t, IsValidFlowRule(goodRule1)) + badRule1 := &Rule{Threshold: 1, Resource: ""} + badRule2 := &Rule{Threshold: -1.9, Resource: "test"} + badRule3 := &Rule{Threshold: 5, Resource: "test", TokenCalculateStrategy: WarmUp, ControlBehavior: Reject} + goodRule1 := &Rule{Threshold: 10, Resource: "test", TokenCalculateStrategy: WarmUp, ControlBehavior: Throttling, WarmUpPeriodSec: 10, MaxQueueingTimeMs: 10} + badRule4 := &Rule{Threshold: 5, Resource: "test", TokenCalculateStrategy: WarmUp, ControlBehavior: Reject, StatIntervalInMs: 6000000} + + assert.Error(t, IsValidRule(badRule1)) + assert.Error(t, IsValidRule(badRule2)) + assert.Error(t, IsValidRule(badRule3)) + assert.NoError(t, IsValidRule(goodRule1)) + assert.Error(t, IsValidRule(badRule4)) +} + +func TestGetRules(t *testing.T) { + t.Run("GetRules", func(t *testing.T) { + if err := ClearRules(); err != nil { + t.Fatal(err) + } + r1 := &Rule{ + Resource: "abc1", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, + } + r2 := &Rule{ + Resource: "abc2", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: Direct, + ControlBehavior: Throttling, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 10, + } + if _, err := LoadRules([]*Rule{r1, r2}); err != nil { + t.Fatal(err) + } + + rs1 := GetRules() + if rs1[0].Resource == "abc1" { + assert.True(t, &rs1[0] != r1) + assert.True(t, &rs1[1] != r2) + assert.True(t, reflect.DeepEqual(&rs1[0], r1)) + assert.True(t, reflect.DeepEqual(&rs1[1], r2)) + } else { + assert.True(t, &rs1[0] != r2) + assert.True(t, &rs1[1] != r1) + assert.True(t, reflect.DeepEqual(&rs1[0], r2)) + assert.True(t, reflect.DeepEqual(&rs1[1], r1)) + } + if err := ClearRules(); err != nil { + t.Fatal(err) + } + }) + + t.Run("getRules", func(t *testing.T) { + r1 := &Rule{ + Resource: "abc1", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, + } + r2 := &Rule{ + Resource: "abc2", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: Direct, + ControlBehavior: Throttling, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 10, + } + if _, err := LoadRules([]*Rule{r1, r2}); err != nil { + t.Fatal(err) + } + rs2 := getRules() + if rs2[0].Resource == "abc1" { + assert.True(t, rs2[0] == r1) + assert.True(t, rs2[1] == r2) + assert.True(t, reflect.DeepEqual(rs2[0], r1)) + assert.True(t, reflect.DeepEqual(rs2[1], r2)) + } else { + assert.True(t, rs2[0] == r2) + assert.True(t, rs2[1] == r1) + assert.True(t, reflect.DeepEqual(rs2[0], r2)) + assert.True(t, reflect.DeepEqual(rs2[1], r1)) + } + if err := ClearRules(); err != nil { + t.Fatal(err) + } + }) +} + +func TestLoadRules(t *testing.T) { + t.Run("loadSameRules", func(t *testing.T) { + _, err := LoadRules([]*Rule{ + { + Resource: "some-test", + Threshold: 10, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + }, + }) + assert.Nil(t, err) + ok, err := LoadRules([]*Rule{ + { + Resource: "some-test", + Threshold: 10, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + }, + }) + assert.Nil(t, err) + assert.False(t, ok) + }) +} + +func Test_generateStatFor(t *testing.T) { + t.Run("generateStatFor_reuse_default_metric_stat", func(t *testing.T) { + r1 := &Rule{ + Resource: "abc", + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + StatIntervalInMs: 0, + Threshold: 100, + RelationStrategy: CurrentResource, + } + // global: 10000ms, 20 sample, bucketLen: 500ms + // metric: 1000ms, 2 sample, bucketLen: 500ms + boundStat, err := generateStatFor(r1) + if err != nil { + t.Fatal(err) + } + assert.True(t, boundStat.reuseResourceStat && boundStat.writeOnlyMetric == nil) + ps, succ := boundStat.readOnlyMetric.(*sbase.SlidingWindowMetric) + assert.True(t, succ) + + resNode := stat.GetResourceNode("abc") + assert.True(t, reflect.DeepEqual(ps, resNode.DefaultMetric())) + }) + + t.Run("generateStatFor_reuse_global_stat", func(t *testing.T) { + // global: 10000ms, 20 sample, bucketLen: 500ms + // metric: 1000ms, 2 sample, bucketLen: 500ms + r1 := &Rule{ + Resource: "abc", + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + StatIntervalInMs: 5000, + Threshold: 100, + RelationStrategy: CurrentResource, + RefResource: "", + } + // global: 10000ms, 20 sample, bucketLen: 500ms + // metric: 1000ms, 2 sample, bucketLen: 500ms + boundStat, err := generateStatFor(r1) + if err != nil { + t.Fatal(err) + } + assert.True(t, boundStat.reuseResourceStat && boundStat.writeOnlyMetric == nil) + ps, succ := boundStat.readOnlyMetric.(*sbase.SlidingWindowMetric) + assert.True(t, succ) + resNode := stat.GetResourceNode("abc") + assert.True(t, !reflect.DeepEqual(ps, resNode.DefaultMetric())) + }) + + t.Run("generateStatFor_standalone_stat", func(t *testing.T) { + // global: 10000ms, 20 sample, bucketLen: 500ms + // metric: 1000ms, 2 sample, bucketLen: 500ms + r1 := &Rule{ + Resource: "abc", + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + StatIntervalInMs: 50000, + Threshold: 100, + RelationStrategy: CurrentResource, + } + // global: 10000ms, 20 sample, bucketLen: 500ms + // metric: 1000ms, 2 sample, bucketLen: 500ms + boundStat, err := generateStatFor(r1) + if err != nil { + t.Fatal(err) + } + assert.True(t, boundStat.reuseResourceStat == false && boundStat.writeOnlyMetric != nil) + }) +} + +func Test_buildRulesOfRes(t *testing.T) { + t.Run("Test_buildRulesOfRes_no_reuse_stat", func(t *testing.T) { + r1 := &Rule{ + Resource: "abc1", + Threshold: 100, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + } + r2 := &Rule{ + Resource: "abc1", + Threshold: 200, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Throttling, + MaxQueueingTimeMs: 10, + } + assert.True(t, len(tcMap["abc1"]) == 0) + tcs := buildRulesOfRes("abc1", []*Rule{r1, r2}) + assert.True(t, len(tcs) == 2) + assert.True(t, tcs[0].BoundRule() == r1) + assert.True(t, tcs[1].BoundRule() == r2) + assert.True(t, reflect.DeepEqual(tcs[0].BoundRule(), r1)) + assert.True(t, reflect.DeepEqual(tcs[1].BoundRule(), r2)) + }) + + t.Run("Test_buildRulesOfRes_reuse_stat", func(t *testing.T) { + // reuse + r1 := &Rule{ + Resource: "abc1", + Threshold: 100, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + StatIntervalInMs: 1000, + } + // reuse + r2 := &Rule{ + Resource: "abc1", + Threshold: 200, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Throttling, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 2000, + } + // reuse + r3 := &Rule{ + Resource: "abc1", + Threshold: 300, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 5000, + } + // independent statistic + r4 := &Rule{ + Resource: "abc1", + Threshold: 400, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 50000, + } + + s1, err := generateStatFor(r1) + if err != nil { + t.Fatal(err) + } + fakeTc1 := &TrafficShapingController{flowCalculator: nil, flowChecker: nil, rule: r1, boundStat: *s1} + s2, err := generateStatFor(r2) + if err != nil { + t.Fatal(err) + } + fakeTc2 := &TrafficShapingController{flowCalculator: nil, flowChecker: nil, rule: r2, boundStat: *s2} + s3, err := generateStatFor(r3) + if err != nil { + t.Fatal(err) + } + fakeTc3 := &TrafficShapingController{flowCalculator: nil, flowChecker: nil, rule: r3, boundStat: *s3} + s4, err := generateStatFor(r4) + if err != nil { + t.Fatal(err) + } + fakeTc4 := &TrafficShapingController{flowCalculator: nil, flowChecker: nil, rule: r4, boundStat: *s4} + tcMap["abc1"] = []*TrafficShapingController{fakeTc1, fakeTc2, fakeTc3, fakeTc4} + assert.True(t, len(tcMap["abc1"]) == 4) + stat1 := tcMap["abc1"][0].boundStat + stat2 := tcMap["abc1"][1].boundStat + oldTc3 := tcMap["abc1"][2] + assert.True(t, tcMap["abc1"][3].boundStat.writeOnlyMetric != nil) + assert.True(t, !tcMap["abc1"][3].boundStat.reuseResourceStat) + stat4 := tcMap["abc1"][3].boundStat + + // reuse stat + r12 := &Rule{ + Resource: "abc1", + Threshold: 300, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + StatIntervalInMs: 1000, + } + // not reusable, generate from resource's global statistic + r22 := &Rule{ + Resource: "abc1", + Threshold: 400, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Throttling, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 10000, + } + // equals + r32 := &Rule{ + Resource: "abc1", + Threshold: 300, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 5000, + } + // reuse independent stat + r42 := &Rule{ + Resource: "abc1", + Threshold: 4000, + RelationStrategy: CurrentResource, + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + MaxQueueingTimeMs: 10, + StatIntervalInMs: 50000, + } + tcs := buildRulesOfRes("abc1", []*Rule{r12, r22, r32, r42}) + assert.True(t, len(tcs) == 4) + assert.True(t, tcs[0].BoundRule() == r12) + assert.True(t, tcs[1].BoundRule() == r22) + assert.True(t, tcs[2].BoundRule() == r3) + assert.True(t, tcs[3].BoundRule() == r42) + assert.True(t, reflect.DeepEqual(tcs[0].BoundRule(), r12)) + assert.True(t, reflect.DeepEqual(tcs[1].BoundRule(), r22)) + assert.True(t, reflect.DeepEqual(tcs[2].BoundRule(), r32) && reflect.DeepEqual(tcs[2].BoundRule(), r3)) + assert.True(t, reflect.DeepEqual(tcs[3].BoundRule(), r42)) + assert.True(t, tcs[0].boundStat == stat1) + assert.True(t, tcs[1].boundStat != stat2) + assert.True(t, tcs[2] == oldTc3) + assert.True(t, tcs[3].boundStat == stat4) + }) } diff --git a/core/flow/slot.go b/core/flow/slot.go index 36ab45256..d7b5eccb9 100644 --- a/core/flow/slot.go +++ b/core/flow/slot.go @@ -6,27 +6,24 @@ import ( "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/core/stat" "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" ) -// FlowSlot -type FlowSlot struct { +type Slot struct { } -func (s *FlowSlot) Check(ctx *base.EntryContext) *base.TokenResult { +func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { res := ctx.Resource.Name() tcs := getTrafficControllerListFor(res) result := ctx.RuleCheckResult - if len(tcs) == 0 { - return result - } // Check rules in order for _, tc := range tcs { if tc == nil { - logging.Warnf("nil traffic controller found, res: %s", res) + logging.Warn("nil traffic controller found", "resourceName", res) continue } - r := canPassCheck(tc, ctx.StatNode, ctx.Input.AcquireCount) + r := canPassCheck(tc, ctx.StatNode, ctx.Input.BatchCount) if r == nil { // nil means pass continue @@ -45,28 +42,28 @@ func (s *FlowSlot) Check(ctx *base.EntryContext) *base.TokenResult { return result } -func canPassCheck(tc *TrafficShapingController, node base.StatNode, acquireCount uint32) *base.TokenResult { - return canPassCheckWithFlag(tc, node, acquireCount, 0) +func canPassCheck(tc *TrafficShapingController, node base.StatNode, batchCount uint32) *base.TokenResult { + return canPassCheckWithFlag(tc, node, batchCount, 0) } -func canPassCheckWithFlag(tc *TrafficShapingController, node base.StatNode, acquireCount uint32, flag int32) *base.TokenResult { - if tc.rule.ClusterMode { - // TODO: support cluster mode - } - return checkInLocal(tc, node, acquireCount, flag) +func canPassCheckWithFlag(tc *TrafficShapingController, node base.StatNode, batchCount uint32, flag int32) *base.TokenResult { + return checkInLocal(tc, node, batchCount, flag) } -func selectNodeByRelStrategy(rule *FlowRule, node base.StatNode) base.StatNode { +func selectNodeByRelStrategy(rule *Rule, node base.StatNode) base.StatNode { if rule.RelationStrategy == AssociatedResource { return stat.GetResourceNode(rule.RefResource) } return node } -func checkInLocal(tc *TrafficShapingController, node base.StatNode, acquireCount uint32, flag int32) *base.TokenResult { - actual := selectNodeByRelStrategy(tc.rule, node) +func checkInLocal(tc *TrafficShapingController, resStat base.StatNode, batchCount uint32, flag int32) *base.TokenResult { + actual := selectNodeByRelStrategy(tc.rule, resStat) if actual == nil { + logging.FrequentErrorOnce.Do(func() { + logging.Error(errors.Errorf("nil resource node"), "no resource node for flow rule", "rule", tc.rule) + }) return base.NewTokenResultPass() } - return tc.PerformChecking(node, acquireCount, flag) + return tc.PerformChecking(actual, batchCount, flag) } diff --git a/core/flow/slot_test.go b/core/flow/slot_test.go new file mode 100644 index 000000000..bceb9a243 --- /dev/null +++ b/core/flow/slot_test.go @@ -0,0 +1,54 @@ +package flow + +import ( + "testing" + + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/core/stat" + "github.com/alibaba/sentinel-golang/logging" + "github.com/stretchr/testify/assert" +) + +func Test_FlowSlot_StandaloneStat(t *testing.T) { + slot := &Slot{} + statSLot := &StandaloneStatSlot{} + res := base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound) + resNode := stat.GetOrCreateResourceNode("abc", base.ResTypeCommon) + ctx := &base.EntryContext{ + Resource: res, + StatNode: resNode, + Input: &base.SentinelInput{ + BatchCount: 1, + }, + RuleCheckResult: nil, + Data: nil, + } + + slot.Check(ctx) + + r1 := &Rule{ + Resource: "abc", + TokenCalculateStrategy: Direct, + ControlBehavior: Reject, + // Use standalone statistic, using single-bucket-sliding-windows + StatIntervalInMs: 20000, + Threshold: 100, + RelationStrategy: CurrentResource, + } + _, e := LoadRules([]*Rule{r1}) + if e != nil { + logging.Error(e, "") + t.Fail() + return + } + + for i := 0; i < 50; i++ { + ret := slot.Check(ctx) + if ret != nil { + t.Fail() + return + } + statSLot.OnEntryPassed(ctx) + } + assert.True(t, getTrafficControllerListFor("abc")[0].boundStat.readOnlyMetric.GetSum(base.MetricEventPass) == 50) +} diff --git a/core/flow/standalone_stat_slot.go b/core/flow/standalone_stat_slot.go new file mode 100644 index 000000000..08164cbbe --- /dev/null +++ b/core/flow/standalone_stat_slot.go @@ -0,0 +1,31 @@ +package flow + +import ( + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" +) + +type StandaloneStatSlot struct { +} + +func (s StandaloneStatSlot) OnEntryPassed(ctx *base.EntryContext) { + res := ctx.Resource.Name() + for _, tc := range getTrafficControllerListFor(res) { + if !tc.boundStat.reuseResourceStat { + if tc.boundStat.writeOnlyMetric != nil { + tc.boundStat.writeOnlyMetric.AddCount(base.MetricEventPass, int64(ctx.Input.BatchCount)) + } else { + logging.Error(errors.New("nil independent write statistic"), "flow module: nil statistic for traffic control", "rule", tc.rule) + } + } + } +} + +func (s StandaloneStatSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { + // Do nothing +} + +func (s StandaloneStatSlot) OnCompleted(ctx *base.EntryContext) { + // Do nothing +} diff --git a/core/flow/tc_default.go b/core/flow/tc_default.go index 2980e876c..f2807af1e 100644 --- a/core/flow/tc_default.go +++ b/core/flow/tc_default.go @@ -4,37 +4,49 @@ import ( "github.com/alibaba/sentinel-golang/core/base" ) -type DefaultTrafficShapingCalculator struct { +type DirectTrafficShapingCalculator struct { + owner *TrafficShapingController threshold float64 } -func NewDefaultTrafficShapingCalculator(threshold float64) *DefaultTrafficShapingCalculator { - return &DefaultTrafficShapingCalculator{threshold: threshold} +func NewDirectTrafficShapingCalculator(owner *TrafficShapingController, threshold float64) *DirectTrafficShapingCalculator { + return &DirectTrafficShapingCalculator{ + owner: owner, + threshold: threshold, + } } -func (d *DefaultTrafficShapingCalculator) CalculateAllowedTokens(base.StatNode, uint32, int32) float64 { +func (d *DirectTrafficShapingCalculator) CalculateAllowedTokens(uint32, int32) float64 { return d.threshold } -type DefaultTrafficShapingChecker struct { - rule *FlowRule +func (d *DirectTrafficShapingCalculator) BoundOwner() *TrafficShapingController { + return d.owner } -func NewDefaultTrafficShapingChecker(rule *FlowRule) *DefaultTrafficShapingChecker { - return &DefaultTrafficShapingChecker{rule: rule} +type RejectTrafficShapingChecker struct { + owner *TrafficShapingController + rule *Rule } -func (d *DefaultTrafficShapingChecker) DoCheck(node base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult { - if node == nil { - return nil +func NewRejectTrafficShapingChecker(owner *TrafficShapingController, rule *Rule) *RejectTrafficShapingChecker { + return &RejectTrafficShapingChecker{ + owner: owner, + rule: rule, } - var curCount float64 - if d.rule.MetricType == Concurrency { - curCount = float64(node.CurrentGoroutineNum()) - } else { - curCount = node.GetQPS(base.MetricEventPass) +} + +func (d *RejectTrafficShapingChecker) BoundOwner() *TrafficShapingController { + return d.owner +} + +func (d *RejectTrafficShapingChecker) DoCheck(resStat base.StatNode, batchCount uint32, threshold float64) *base.TokenResult { + metricReadonlyStat := d.BoundOwner().boundStat.readOnlyMetric + if metricReadonlyStat == nil { + return nil } - if curCount+float64(acquireCount) > threshold { + curCount := float64(metricReadonlyStat.GetSum(base.MetricEventPass)) + if curCount+float64(batchCount) > threshold { return base.NewTokenResultBlockedWithCause(base.BlockTypeFlow, "", d.rule, curCount) } return nil diff --git a/core/flow/tc_throttling.go b/core/flow/tc_throttling.go index f6d0fea05..b7b1ec704 100644 --- a/core/flow/tc_throttling.go +++ b/core/flow/tc_throttling.go @@ -13,24 +13,25 @@ const nanoUnitOffset = time.Second / time.Nanosecond // ThrottlingChecker limits the time interval between two requests. type ThrottlingChecker struct { + owner *TrafficShapingController maxQueueingTimeNs uint64 - - lastPassedTime uint64 - - // TODO: support strict mode - strict bool + lastPassedTime uint64 } -func NewThrottlingChecker(timeoutMs uint32) *ThrottlingChecker { +func NewThrottlingChecker(owner *TrafficShapingController, timeoutMs uint32) *ThrottlingChecker { return &ThrottlingChecker{ + owner: owner, maxQueueingTimeNs: uint64(timeoutMs) * util.UnixTimeUnitOffset, lastPassedTime: 0, } } +func (c *ThrottlingChecker) BoundOwner() *TrafficShapingController { + return c.owner +} -func (c *ThrottlingChecker) DoCheck(_ base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult { - // Pass when acquire count is less or equal than 0. - if acquireCount <= 0 { +func (c *ThrottlingChecker) DoCheck(_ base.StatNode, batchCount uint32, threshold float64) *base.TokenResult { + // Pass when batch count is less or equal than 0. + if batchCount <= 0 { return nil } if threshold <= 0 { @@ -39,7 +40,7 @@ func (c *ThrottlingChecker) DoCheck(_ base.StatNode, acquireCount uint32, thresh // Here we use nanosecond so that we could control the queueing time more accurately. curNano := util.CurrentTimeNano() // The interval between two requests (in nanoseconds). - interval := uint64(math.Ceil(float64(acquireCount) / threshold * float64(nanoUnitOffset))) + interval := uint64(math.Ceil(float64(batchCount) / threshold * float64(nanoUnitOffset))) // Expected pass time of this request. expectedTime := atomic.LoadUint64(&c.lastPassedTime) + interval diff --git a/core/flow/tc_throttling_test.go b/core/flow/tc_throttling_test.go index 6481cc253..75676de2b 100644 --- a/core/flow/tc_throttling_test.go +++ b/core/flow/tc_throttling_test.go @@ -11,7 +11,7 @@ import ( ) func TestThrottlingChecker_DoCheckNoQueueingSingleThread(t *testing.T) { - tc := NewThrottlingChecker(0) + tc := NewThrottlingChecker(nil, 0) var qps float64 = 5 // The first request will pass. @@ -28,7 +28,7 @@ func TestThrottlingChecker_DoCheckNoQueueingSingleThread(t *testing.T) { } func TestThrottlingChecker_DoCheckSingleThread(t *testing.T) { - tc := NewThrottlingChecker(1000) + tc := NewThrottlingChecker(nil, 1000) var qps float64 = 5 resultList := make([]*base.TokenResult, 0) for i := 0; i < 10; i++ { @@ -48,7 +48,7 @@ func TestThrottlingChecker_DoCheckSingleThread(t *testing.T) { } func TestThrottlingChecker_DoCheckQueueingParallel(t *testing.T) { - tc := NewThrottlingChecker(1000) + tc := NewThrottlingChecker(nil, 1000) var qps float64 = 5 assert.True(t, tc.DoCheck(nil, 1, qps) == nil) diff --git a/core/flow/tc_warm_up.go b/core/flow/tc_warm_up.go index 63841cf03..e15a3f4cf 100644 --- a/core/flow/tc_warm_up.go +++ b/core/flow/tc_warm_up.go @@ -4,14 +4,14 @@ import ( "math" "sync/atomic" - "github.com/alibaba/sentinel-golang/logging" - "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/core/config" + "github.com/alibaba/sentinel-golang/logging" "github.com/alibaba/sentinel-golang/util" ) type WarmUpTrafficShapingCalculator struct { + owner *TrafficShapingController threshold float64 warmUpPeriodInSec uint32 coldFactor uint32 @@ -22,25 +22,30 @@ type WarmUpTrafficShapingCalculator struct { lastFilledTime uint64 } -func NewWarmUpTrafficShapingCalculator(rule *FlowRule) *WarmUpTrafficShapingCalculator { +func (c *WarmUpTrafficShapingCalculator) BoundOwner() *TrafficShapingController { + return c.owner +} + +func NewWarmUpTrafficShapingCalculator(owner *TrafficShapingController, rule *Rule) TrafficShapingCalculator { if rule.WarmUpColdFactor <= 1 { rule.WarmUpColdFactor = config.DefaultWarmUpColdFactor - logging.Warnf("[NewWarmUpTrafficShapingCalculator] invalid WarmUpColdFactor,use default values: %d", config.DefaultWarmUpColdFactor) + logging.Warn("[NewWarmUpTrafficShapingCalculator] No set WarmUpColdFactor,use default warm up cold factor value", "defaultWarmUpColdFactor", config.DefaultWarmUpColdFactor) } - warningToken := uint64((float64(rule.WarmUpPeriodSec) * rule.Count) / float64(rule.WarmUpColdFactor-1)) + warningToken := uint64((float64(rule.WarmUpPeriodSec) * rule.Threshold) / float64(rule.WarmUpColdFactor-1)) - maxToken := warningToken + uint64(2*float64(rule.WarmUpPeriodSec)*rule.Count/float64(1.0+rule.WarmUpColdFactor)) + maxToken := warningToken + uint64(2*float64(rule.WarmUpPeriodSec)*rule.Threshold/float64(1.0+rule.WarmUpColdFactor)) - slope := float64(rule.WarmUpColdFactor-1.0) / rule.Count / float64(maxToken-warningToken) + slope := float64(rule.WarmUpColdFactor-1.0) / rule.Threshold / float64(maxToken-warningToken) warmUpTrafficShapingCalculator := &WarmUpTrafficShapingCalculator{ + owner: owner, warmUpPeriodInSec: rule.WarmUpPeriodSec, coldFactor: rule.WarmUpColdFactor, warningToken: warningToken, maxToken: maxToken, slope: slope, - threshold: rule.Count, + threshold: rule.Threshold, storedTokens: 0, lastFilledTime: 0, } @@ -48,8 +53,9 @@ func NewWarmUpTrafficShapingCalculator(rule *FlowRule) *WarmUpTrafficShapingCalc return warmUpTrafficShapingCalculator } -func (c *WarmUpTrafficShapingCalculator) CalculateAllowedTokens(node base.StatNode, acquireCount uint32, flag int32) float64 { - previousQps := node.GetPreviousQPS(base.MetricEventPass) +func (c *WarmUpTrafficShapingCalculator) CalculateAllowedTokens(_ uint32, _ int32) float64 { + metricReadonlyStat := c.BoundOwner().boundStat.readOnlyMetric + previousQps := metricReadonlyStat.GetPreviousQPS(base.MetricEventPass) c.syncToken(previousQps) restToken := atomic.LoadInt64(&c.storedTokens) diff --git a/core/flow/traffic_shaping.go b/core/flow/traffic_shaping.go index c558abd88..a93d5f7f6 100644 --- a/core/flow/traffic_shaping.go +++ b/core/flow/traffic_shaping.go @@ -7,28 +7,45 @@ import ( // TrafficShapingCalculator calculates the actual traffic shaping threshold // based on the threshold of rule and the traffic shaping strategy. type TrafficShapingCalculator interface { - CalculateAllowedTokens(node base.StatNode, acquireCount uint32, flag int32) float64 + BoundOwner() *TrafficShapingController + CalculateAllowedTokens(batchCount uint32, flag int32) float64 } // TrafficShapingChecker performs checking according to current metrics and the traffic // shaping strategy, then yield the token result. type TrafficShapingChecker interface { - DoCheck(node base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult + BoundOwner() *TrafficShapingController + DoCheck(resStat base.StatNode, batchCount uint32, threshold float64) *base.TokenResult +} + +// standaloneStatistic indicates the independent statistic for each TrafficShapingController +type standaloneStatistic struct { + // reuseResourceStat indicates whether current standaloneStatistic reuse the current resource's global statistic + reuseResourceStat bool + // readOnlyMetric is the readonly metric statistic. + // if reuseResourceStat is true, it would be the reused SlidingWindowMetric + // if reuseResourceStat is false, it would be the BucketLeapArray + readOnlyMetric base.ReadStat + // writeOnlyMetric is the write only metric statistic. + // if reuseResourceStat is true, it would be nil + // if reuseResourceStat is false, it would be the BucketLeapArray + writeOnlyMetric base.WriteStat } type TrafficShapingController struct { flowCalculator TrafficShapingCalculator flowChecker TrafficShapingChecker - rule *FlowRule + rule *Rule + // boundStat is the statistic of current TrafficShapingController + boundStat standaloneStatistic } -// NewTrafficShapingController creates a TrafficShapingController wrapped with the given checker and flow rule. -func NewTrafficShapingController(flowCalculator TrafficShapingCalculator, flowChecker TrafficShapingChecker, rule *FlowRule) *TrafficShapingController { - return &TrafficShapingController{flowCalculator: flowCalculator, flowChecker: flowChecker, rule: rule} +func NewTrafficShapingController(rule *Rule, boundStat *standaloneStatistic) (*TrafficShapingController, error) { + return &TrafficShapingController{rule: rule, boundStat: *boundStat}, nil } -func (t *TrafficShapingController) Rule() *FlowRule { +func (t *TrafficShapingController) BoundRule() *Rule { return t.rule } @@ -40,7 +57,7 @@ func (t *TrafficShapingController) FlowCalculator() TrafficShapingCalculator { return t.flowCalculator } -func (t *TrafficShapingController) PerformChecking(node base.StatNode, acquireCount uint32, flag int32) *base.TokenResult { - allowedTokens := t.flowCalculator.CalculateAllowedTokens(node, acquireCount, flag) - return t.flowChecker.DoCheck(node, acquireCount, allowedTokens) +func (t *TrafficShapingController) PerformChecking(resStat base.StatNode, batchCount uint32, flag int32) *base.TokenResult { + allowedTokens := t.flowCalculator.CalculateAllowedTokens(batchCount, flag) + return t.flowChecker.DoCheck(resStat, batchCount, allowedTokens) } diff --git a/core/hotspot/cache/lru.go b/core/hotspot/cache/lru.go index 92f34c374..c369f0d02 100644 --- a/core/hotspot/cache/lru.go +++ b/core/hotspot/cache/lru.go @@ -3,7 +3,6 @@ package cache import ( "container/list" - "github.com/alibaba/sentinel-golang/logging" "github.com/pkg/errors" ) @@ -193,12 +192,10 @@ func (c *LRU) removeOldest() { func (c *LRU) removeElement(e *list.Element) { c.evictList.Remove(e) if e.Value == nil { - logging.Errorf("The Value of evictList's Element is nil.") return } kv, ok := e.Value.(*entry) if !ok { - logging.Errorf("Fail to assert the Value of evictList's Element as *entry.") return } delete(c.items, kv.key) diff --git a/core/hotspot/concurrency_stat_slot.go b/core/hotspot/concurrency_stat_slot.go index 8c3574743..65fc03d65 100644 --- a/core/hotspot/concurrency_stat_slot.go +++ b/core/hotspot/concurrency_stat_slot.go @@ -22,7 +22,7 @@ func (c *ConcurrencyStatSlot) OnEntryPassed(ctx *base.EntryContext) { metric := tc.BoundMetric() concurrencyPtr, existed := metric.ConcurrencyCounter.Get(arg) if !existed || concurrencyPtr == nil { - logging.Debugf("Parameter %+v does not exist in ConcurrencyCounter.", arg) + logging.Debug("Parameter does not exist in ConcurrencyCounter.", "argument", arg) continue } util.IncrementAndGetInt64(concurrencyPtr) @@ -45,7 +45,7 @@ func (c *ConcurrencyStatSlot) OnCompleted(ctx *base.EntryContext) { metric := tc.BoundMetric() concurrencyPtr, existed := metric.ConcurrencyCounter.Get(arg) if !existed || concurrencyPtr == nil { - logging.Debugf("Parameter: %+v does not exist in ConcurrencyCounter.", arg) + logging.Debug("Parameter does not exist in ConcurrencyCounter.", "argument", arg) continue } util.DecrementAndGetInt64(concurrencyPtr) diff --git a/core/hotspot/rule.go b/core/hotspot/rule.go index a607af4a7..52a347b72 100644 --- a/core/hotspot/rule.go +++ b/core/hotspot/rule.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" ) // ControlBehavior indicates the traffic shaping behaviour. @@ -149,7 +150,7 @@ func parseSpecificItems(source []SpecificValue) map[interface{}]int64 { case KindInt: realVal, err := strconv.Atoi(item.ValStr) if err != nil { - logging.Errorf("Failed to parse value for int specific item. paramKind: %+v, value: %s, err: %+v", item.ValKind, item.ValStr, err) + logging.Error(errors.Wrap(err, "parseSpecificItems error"), "Failed to parse value for int specific item", "itemValKind", item.ValKind, "itemValStr", item.ValStr) continue } ret[realVal] = item.Threshold @@ -160,7 +161,7 @@ func parseSpecificItems(source []SpecificValue) map[interface{}]int64 { case KindBool: realVal, err := strconv.ParseBool(item.ValStr) if err != nil { - logging.Errorf("Failed to parse value for bool specific item. value: %s, err: %+v", item.ValStr, err) + logging.Error(errors.Wrap(err, "parseSpecificItems error"), "Failed to parse value for bool specific item", "itemValStr", item.ValStr) continue } ret[realVal] = item.Threshold @@ -168,17 +169,17 @@ func parseSpecificItems(source []SpecificValue) map[interface{}]int64 { case KindFloat64: realVal, err := strconv.ParseFloat(item.ValStr, 64) if err != nil { - logging.Errorf("Failed to parse value for float specific item. value: %s, err: %+v", item.ValStr, err) + logging.Error(errors.Wrap(err, "parseSpecificItems error"), "Failed to parse value for float specific item", "itemValStr", item.ValStr) continue } realVal, err = strconv.ParseFloat(fmt.Sprintf("%.5f", realVal), 64) if err != nil { - logging.Errorf("Failed to parse value for float specific item. value: %s, err: %+v", item.ValStr, err) + logging.Error(errors.Wrap(err, "parseSpecificItems error"), "Failed to parse value for float specific item", "itemValStr", item.ValStr) continue } ret[realVal] = item.Threshold default: - logging.Errorf("Unsupported kind for specific item: %d", item.ValKind) + logging.Error(errors.New("Unsupported kind for specific item"), "", item.ValKind) } } return ret diff --git a/core/hotspot/rule_manager.go b/core/hotspot/rule_manager.go index 3f41039bb..665a15241 100644 --- a/core/hotspot/rule_manager.go +++ b/core/hotspot/rule_manager.go @@ -2,7 +2,6 @@ package hotspot import ( "fmt" - "strings" "sync" "github.com/alibaba/sentinel-golang/logging" @@ -68,14 +67,34 @@ func LoadRules(rules []*Rule) (bool, error) { return true, err } -// GetRules returns existing rules of the given resource. -func GetRules(res string) []*Rule { +// GetRules returns all the rules based on copy. +// It doesn't take effect for hotspot module if user changes the rule. +// GetRules need to compete hotspot module's global lock and the high performance losses of copy, +// reduce or do not call GetRules if possible +func GetRules() []Rule { + tcMux.RLock() + rules := rulesFrom(tcMap) + tcMux.RUnlock() + + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) + } + return ret +} + +// GetRulesOfResource returns specific resource's rules based on copy. +// It doesn't take effect for hotspot module if user changes the rule. +// GetRulesOfResource need to compete hotspot module's global lock and the high performance losses of copy, +// reduce or do not call GetRulesOfResource frequently if possible +func GetRulesOfResource(res string) []Rule { tcMux.RLock() - defer tcMux.RUnlock() resTcs := tcMap[res] - ret := make([]*Rule, 0, len(resTcs)) + tcMux.RUnlock() + + ret := make([]Rule, 0, len(resTcs)) for _, tc := range resTcs { - ret = append(ret, tc.BoundRule()) + ret = append(ret, *tc.BoundRule()) } return ret } @@ -100,7 +119,7 @@ func onRuleUpdate(rules []*Rule) (err error) { newRuleMap := make(map[string][]*Rule) for _, r := range rules { if err := IsValidRule(r); err != nil { - logging.Warnf("Ignoring invalid hotspot rule when loading new rules, rule: %s, reason: %s", r.String(), err.Error()) + logging.Warn("Ignoring invalid hotspot rule when loading new rules", "rule", r, "err", err) continue } res := r.ResourceName() @@ -124,7 +143,7 @@ func onRuleUpdate(rules []*Rule) (err error) { if r := recover(); r != nil { return } - logging.Debugf("Updating hotspot rule spends %d ns.", util.CurrentTimeNano()-start) + logging.Debug("time statistic(ns) for updating hotspot rule", "timeCost", util.CurrentTimeNano()-start) logRuleUpdate(m) }() @@ -149,7 +168,7 @@ func onRuleUpdate(rules []*Rule) (err error) { // generate new traffic shaping controller generator, supported := tcGenFuncMap[r.ControlBehavior] if !supported { - logging.Warnf("Ignoring the frequent param flow rule due to unsupported control behavior: %v", r) + logging.Warn("Ignoring the frequent param flow rule due to unsupported control behavior", "rule", r) continue } var tc TrafficShapingController @@ -160,7 +179,7 @@ func onRuleUpdate(rules []*Rule) (err error) { tc = generator(r, nil) } if tc == nil { - logging.Debugf("Ignoring the frequent param flow rule due to bad generated traffic controller: %v", r) + logging.Debug("Ignoring the frequent param flow rule due to bad generated traffic controller", "rule", r) continue } @@ -177,14 +196,12 @@ func onRuleUpdate(rules []*Rule) (err error) { } func logRuleUpdate(m trafficControllerMap) { - sb := strings.Builder{} - sb.WriteString("Hotspot parameter flow control rules loaded: [") - - for _, r := range rulesFrom(m) { - sb.WriteString(r.String() + ",") + rs := rulesFrom(m) + if len(rs) == 0 { + logging.Info("[HotspotRuleManager] Hotspot rules were cleared") + } else { + logging.Info("[HotspotRuleManager] Hotspot rules were loaded", "rules", rs) } - sb.WriteString("]") - logging.Info(sb.String()) } func rulesFrom(m trafficControllerMap) []*Rule { diff --git a/core/hotspot/rule_manager_test.go b/core/hotspot/rule_manager_test.go index 2c583eec0..f53916c14 100644 --- a/core/hotspot/rule_manager_test.go +++ b/core/hotspot/rule_manager_test.go @@ -34,7 +34,9 @@ func Test_tcGenFuncMap(t *testing.T) { tc := generator(r1, nil) assert.True(t, tc.BoundMetric() != nil && tc.BoundRule() == r1 && tc.BoundParamIndex() == 0) rejectTC := tc.(*rejectTrafficShapingController) - assert.True(t, rejectTC != nil) + if rejectTC == nil { + t.Fatal("nil traffic shaping controller") + } assert.True(t, rejectTC.res == r1.Resource && rejectTC.metricType == r1.MetricType && rejectTC.paramIndex == r1.ParamIndex && rejectTC.burstCount == r1.BurstCount) assert.True(t, rejectTC.threshold == r1.Threshold && rejectTC.durationInSec == r1.DurationInSec) }) @@ -75,7 +77,9 @@ func Test_tcGenFuncMap(t *testing.T) { tc := generator(r1, metric) assert.True(t, tc.BoundMetric() != nil && tc.BoundRule() == r1 && tc.BoundParamIndex() == 0) rejectTC := tc.(*rejectTrafficShapingController) - assert.True(t, rejectTC != nil) + if rejectTC == nil { + t.Fatal("nil traffic shaping controller") + } assert.True(t, rejectTC.metric == metric) assert.True(t, rejectTC.res == r1.Resource && rejectTC.metricType == r1.MetricType && rejectTC.paramIndex == r1.ParamIndex && rejectTC.burstCount == r1.BurstCount) assert.True(t, rejectTC.threshold == r1.Threshold && rejectTC.durationInSec == r1.DurationInSec) diff --git a/core/hotspot/slot.go b/core/hotspot/slot.go index 39733b3b8..769330d38 100644 --- a/core/hotspot/slot.go +++ b/core/hotspot/slot.go @@ -23,11 +23,11 @@ func matchArg(tc TrafficShapingController, args []interface{}) interface{} { idx = len(args) + idx } if idx < 0 { - logging.Debugf("The param index in tc(%+v) is invalid for args(%+v)", tc, args) + logging.Debug("The param index of hotspot traffic shaping controller is invalid", "args", args, "paramIndex", tc.BoundParamIndex()) return nil } if idx >= len(args) { - logging.Debugf("The argument doesn't exist for index(%d) of tc(%+v), args: %+v", idx, tc, args) + logging.Debug("The argument in index doesn't exist", "args", args, "paramIndex", tc.BoundParamIndex()) return nil } arg := args[idx] @@ -88,7 +88,7 @@ func matchArg(tc TrafficShapingController, args []interface{}) interface{} { func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { res := ctx.Resource.Name() args := ctx.Input.Args - acquire := int64(ctx.Input.AcquireCount) + batch := int64(ctx.Input.BatchCount) result := ctx.RuleCheckResult tcs := getTrafficControllersFor(res) @@ -101,7 +101,7 @@ func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { if arg == nil { continue } - r := canPassCheck(tc, arg, acquire) + r := canPassCheck(tc, arg, batch) if r == nil { continue } @@ -119,10 +119,10 @@ func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { return result } -func canPassCheck(tc TrafficShapingController, arg interface{}, acquire int64) *base.TokenResult { - return canPassLocalCheck(tc, arg, acquire) +func canPassCheck(tc TrafficShapingController, arg interface{}, batch int64) *base.TokenResult { + return canPassLocalCheck(tc, arg, batch) } -func canPassLocalCheck(tc TrafficShapingController, arg interface{}, acquire int64) *base.TokenResult { - return tc.PerformChecking(arg, acquire) +func canPassLocalCheck(tc TrafficShapingController, arg interface{}, batch int64) *base.TokenResult { + return tc.PerformChecking(arg, batch) } diff --git a/core/hotspot/slot_test.go b/core/hotspot/slot_test.go index 658b902a2..e837cc0f9 100644 --- a/core/hotspot/slot_test.go +++ b/core/hotspot/slot_test.go @@ -15,8 +15,8 @@ type TrafficShapingControllerMock struct { mock.Mock } -func (m *TrafficShapingControllerMock) PerformChecking(arg interface{}, acquireCount int64) *base.TokenResult { - retArgs := m.Called(arg, acquireCount) +func (m *TrafficShapingControllerMock) PerformChecking(arg interface{}, batchCount int64) *base.TokenResult { + retArgs := m.Called(arg, batchCount) return retArgs.Get(0).(*base.TokenResult) } diff --git a/core/hotspot/traffic_shaping.go b/core/hotspot/traffic_shaping.go index 4b45b959e..8ef902390 100644 --- a/core/hotspot/traffic_shaping.go +++ b/core/hotspot/traffic_shaping.go @@ -13,7 +13,7 @@ import ( ) type TrafficShapingController interface { - PerformChecking(arg interface{}, acquireCount int64) *base.TokenResult + PerformChecking(arg interface{}, batchCount int64) *base.TokenResult BoundParamIndex() int @@ -50,14 +50,15 @@ func newBaseTrafficShapingControllerWithMetric(r *Rule, metric *ParamsMetric) *b } func newBaseTrafficShapingController(r *Rule) *baseTrafficShapingController { - var size = 0 + size := 0 if r.ParamsMaxCapacity > 0 { size = int(r.ParamsMaxCapacity) } else { size = int(math.Min(float64(ParamsMaxCapacity), float64(ParamsCapacityBase*r.DurationInSec))) } if size <= 0 { - logging.Warnf("The size of cache is not more than 0, ParamsMaxCapacity: %d, ParamsCapacityBase: %d", ParamsMaxCapacity, ParamsCapacityBase) + logging.Warn("invalid size of cache, so use default value for ParamsMaxCapacity and ParamsCapacityBase", + "ParamsMaxCapacity", ParamsMaxCapacity, "ParamsCapacityBase", ParamsCapacityBase) size = ParamsMaxCapacity } metric := &ParamsMetric{ @@ -118,7 +119,7 @@ func (c *baseTrafficShapingController) BoundParamIndex() int { return c.paramIndex } -func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquireCount int64) *base.TokenResult { +func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, batchCount int64) *base.TokenResult { metric := c.metric if metric == nil { return nil @@ -147,8 +148,8 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir fmt.Sprintf("arg=%v", arg), c.BoundRule(), nil) } maxCount := tokenCount + c.burstCount - if acquireCount > maxCount { - // return blocked because the acquired number is more than max count of rejectTrafficShapingController + if batchCount > maxCount { + // return blocked because the batch number is more than max count of rejectTrafficShapingController return base.NewTokenResultBlockedWithCause(base.BlockTypeHotSpotParamFlow, fmt.Sprintf("arg=%v", arg), c.BoundRule(), nil) } @@ -158,7 +159,7 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir lastAddTokenTimePtr := timeCounter.AddIfAbsent(arg, ¤tTimeInMs) if lastAddTokenTimePtr == nil { // First to fill token, and consume token immediately - leftCount := maxCount - acquireCount + leftCount := maxCount - batchCount tokenCounter.AddIfAbsent(arg, &leftCount) return nil } @@ -167,7 +168,7 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir passTime := currentTimeInMs - atomic.LoadInt64(lastAddTokenTimePtr) if passTime > c.durationInSec*1000 { // Refill the tokens because statistic window has passed. - leftCount := maxCount - acquireCount + leftCount := maxCount - batchCount oldQpsPtr := tokenCounter.AddIfAbsent(arg, &leftCount) if oldQpsPtr == nil { // Might not be accurate here. @@ -179,9 +180,9 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir toAddTokenNum := passTime * tokenCount / (c.durationInSec * 1000) newQps := int64(0) if toAddTokenNum+restQps > maxCount { - newQps = maxCount - acquireCount + newQps = maxCount - batchCount } else { - newQps = toAddTokenNum + restQps - acquireCount + newQps = toAddTokenNum + restQps - batchCount } if newQps < 0 { return base.NewTokenResultBlockedWithCause(base.BlockTypeHotSpotParamFlow, @@ -194,13 +195,13 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir runtime.Gosched() } } else { - //check whether the rest of token is enough to acquire + //check whether the rest of token is enough to batch oldQpsPtr, found := tokenCounter.Get(arg) if found { oldRestToken := atomic.LoadInt64(oldQpsPtr) - if oldRestToken-acquireCount >= 0 { + if oldRestToken-batchCount >= 0 { //update - if atomic.CompareAndSwapInt64(oldQpsPtr, oldRestToken, oldRestToken-acquireCount) { + if atomic.CompareAndSwapInt64(oldQpsPtr, oldRestToken, oldRestToken-batchCount) { return nil } } else { @@ -213,7 +214,7 @@ func (c *rejectTrafficShapingController) PerformChecking(arg interface{}, acquir } } -func (c *throttlingTrafficShapingController) PerformChecking(arg interface{}, acquireCount int64) *base.TokenResult { +func (c *throttlingTrafficShapingController) PerformChecking(arg interface{}, batchCount int64) *base.TokenResult { metric := c.metric if metric == nil { return nil @@ -241,7 +242,7 @@ func (c *throttlingTrafficShapingController) PerformChecking(arg interface{}, ac return base.NewTokenResultBlockedWithCause(base.BlockTypeHotSpotParamFlow, fmt.Sprintf("arg=%v", arg), c.BoundRule(), nil) } - intervalCostTime := int64(math.Round(float64(acquireCount * c.durationInSec * 1000 / tokenCount))) + intervalCostTime := int64(math.Round(float64(batchCount * c.durationInSec * 1000 / tokenCount))) for { currentTimeInMs := int64(util.CurrentTimeMillis()) lastPassTimePtr := timeCounter.AddIfAbsent(arg, ¤tTimeInMs) diff --git a/core/isolation/doc.go b/core/isolation/doc.go new file mode 100644 index 000000000..bb706260a --- /dev/null +++ b/core/isolation/doc.go @@ -0,0 +1,3 @@ +// Package isolation implements the concurrency traffic control. + +package isolation diff --git a/core/isolation/rule.go b/core/isolation/rule.go new file mode 100644 index 000000000..6983a33ef --- /dev/null +++ b/core/isolation/rule.go @@ -0,0 +1,45 @@ +package isolation + +import ( + "encoding/json" + "fmt" +) + +// MetricType represents the target metric type. +type MetricType int32 + +const ( + // Concurrency represents concurrency count. + Concurrency MetricType = iota +) + +func (s MetricType) String() string { + switch s { + case Concurrency: + return "Concurrency" + default: + return "Undefined" + } +} + +// Rule describes the concurrency num control, that is similar to semaphore +type Rule struct { + // ID represents the unique ID of the rule (optional). + ID string `json:"id,omitempty"` + Resource string `json:"resource"` + MetricType MetricType `json:"metricType"` + Threshold uint32 `json:"threshold"` +} + +func (r *Rule) String() string { + b, err := json.Marshal(r) + if err != nil { + // Return the fallback string + return fmt.Sprintf("{Id=%s, Resource=%s, MetricType=%s, Threshold=%d}", r.ID, r.Resource, r.MetricType.String(), r.Threshold) + } + return string(b) +} + +func (r *Rule) ResourceName() string { + return r.Resource +} diff --git a/core/isolation/rule_manager.go b/core/isolation/rule_manager.go new file mode 100644 index 000000000..cb9e02fc5 --- /dev/null +++ b/core/isolation/rule_manager.go @@ -0,0 +1,138 @@ +package isolation + +import ( + "sync" + + "github.com/alibaba/sentinel-golang/logging" + "github.com/alibaba/sentinel-golang/util" + "github.com/pkg/errors" +) + +var ( + ruleMap = make(map[string][]*Rule) + rwMux = &sync.RWMutex{} +) + +// LoadRules loads the given isolation rules to the rule manager, while all previous rules will be replaced. +func LoadRules(rules []*Rule) (updated bool, err error) { + updated = true + err = nil + + m := make(map[string][]*Rule) + for _, r := range rules { + if e := IsValid(r); e != nil { + logging.Error(e, "invalid isolation rule.", "rule", r) + continue + } + resRules, ok := m[r.Resource] + if !ok { + resRules = make([]*Rule, 0, 1) + } + m[r.Resource] = append(resRules, r) + } + + start := util.CurrentTimeNano() + rwMux.Lock() + defer func() { + rwMux.Unlock() + logging.Debug("time statistic(ns) for updating isolation rule", "timeCost", util.CurrentTimeNano()-start) + logRuleUpdate(m) + }() + ruleMap = m + return +} + +// ClearRules clears all the rules in isolation module. +func ClearRules() error { + _, err := LoadRules(nil) + return err +} + +// GetRules returns all the rules based on copy. +// It doesn't take effect for isolation module if user changes the rule. +func GetRules() []Rule { + rules := getRules() + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) + } + return ret +} + +// GetRulesOfResource returns specific resource's rules based on copy. +// It doesn't take effect for isolation module if user changes the rule. +func GetRulesOfResource(res string) []Rule { + rules := getRulesOfResource(res) + ret := make([]Rule, 0, len(rules)) + for _, rule := range rules { + ret = append(ret, *rule) + } + return ret +} + +// getRules returns all the rules。Any changes of rules take effect for isolation module +// getRules is an internal interface. +func getRules() []*Rule { + rwMux.RLock() + defer rwMux.RUnlock() + + return rulesFrom(ruleMap) +} + +// getRulesOfResource returns specific resource's rules。Any changes of rules take effect for isolation module +// getRulesOfResource is an internal interface. +func getRulesOfResource(res string) []*Rule { + rwMux.RLock() + defer rwMux.RUnlock() + + resRules, exist := ruleMap[res] + if !exist { + return nil + } + ret := make([]*Rule, 0, len(resRules)) + for _, r := range resRules { + ret = append(ret, r) + } + return ret +} + +func rulesFrom(m map[string][]*Rule) []*Rule { + rules := make([]*Rule, 0) + if len(m) == 0 { + return rules + } + for _, rs := range m { + for _, r := range rs { + if r != nil { + rules = append(rules, r) + } + } + } + return rules +} + +func logRuleUpdate(m map[string][]*Rule) { + rs := rulesFrom(m) + if len(rs) == 0 { + logging.Info("[IsolationRuleManager] Isolation rules were cleared") + } else { + logging.Info("[IsolationRuleManager] Isolation rules were loaded", "rules", rs) + } +} + +// IsValidRule checks whether the given Rule is valid. +func IsValid(r *Rule) error { + if r == nil { + return errors.New("nil isolation rule") + } + if len(r.Resource) == 0 { + return errors.New("empty resource of isolation rule") + } + if r.MetricType != Concurrency { + return errors.Errorf("unsupported metric type: %d", r.MetricType) + } + if r.Threshold == 0 { + return errors.New("zero threshold") + } + return nil +} diff --git a/core/isolation/rule_manager_test.go b/core/isolation/rule_manager_test.go new file mode 100644 index 000000000..7f55c2dbf --- /dev/null +++ b/core/isolation/rule_manager_test.go @@ -0,0 +1,39 @@ +package isolation + +import ( + "testing" + + "github.com/alibaba/sentinel-golang/logging" + "github.com/stretchr/testify/assert" +) + +func TestLoadRules(t *testing.T) { + t.Run("TestLoadRules_1", func(t *testing.T) { + logging.SetGlobalLoggerLevel(logging.DebugLevel) + r1 := &Rule{ + Resource: "abc1", + MetricType: Concurrency, + Threshold: 100, + } + r2 := &Rule{ + Resource: "abc2", + MetricType: Concurrency, + Threshold: 200, + } + r3 := &Rule{ + Resource: "abc3", + MetricType: MetricType(1), + Threshold: 200, + } + _, err := LoadRules([]*Rule{r1, r2, r3}) + assert.True(t, err == nil) + assert.True(t, len(ruleMap) == 2) + assert.True(t, len(ruleMap["abc1"]) == 1) + assert.True(t, ruleMap["abc1"][0] == r1) + assert.True(t, len(ruleMap["abc2"]) == 1) + assert.True(t, ruleMap["abc2"][0] == r2) + + err = ClearRules() + assert.True(t, err == nil) + }) +} diff --git a/core/isolation/slot.go b/core/isolation/slot.go new file mode 100644 index 000000000..43ff0aa08 --- /dev/null +++ b/core/isolation/slot.go @@ -0,0 +1,47 @@ +package isolation + +import ( + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/logging" + "github.com/pkg/errors" +) + +type Slot struct { +} + +func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { + resource := ctx.Resource.Name() + result := ctx.RuleCheckResult + if len(resource) == 0 { + return result + } + if passed, rule, snapshot := checkPass(ctx); !passed { + if result == nil { + result = base.NewTokenResultBlockedWithCause(base.BlockTypeIsolation, "", rule, snapshot) + } else { + result.ResetToBlockedWithCause(base.BlockTypeIsolation, "", rule, snapshot) + } + } + return result +} + +func checkPass(ctx *base.EntryContext) (bool, *Rule, uint32) { + statNode := ctx.StatNode + batchCount := ctx.Input.BatchCount + curCount := uint32(0) + for _, rule := range getRulesOfResource(ctx.Resource.Name()) { + threshold := rule.Threshold + if rule.MetricType == Concurrency { + if cur := statNode.CurrentGoroutineNum(); cur >= 0 { + curCount = uint32(cur) + } else { + curCount = 0 + logging.Error(errors.New("negative concurrency"), "", "rule", rule) + } + if curCount+batchCount > threshold { + return false, rule, curCount + } + } + } + return true, nil, curCount +} diff --git a/core/log/metric/aggregator.go b/core/log/metric/aggregator.go index 14007d3ec..1ea7ebc13 100644 --- a/core/log/metric/aggregator.go +++ b/core/log/metric/aggregator.go @@ -37,7 +37,7 @@ func InitTask() (err error) { metricWriter, err = NewDefaultMetricLogWriter(config.MetricLogSingleFileMaxSize(), config.MetricLogMaxFileAmount()) if err != nil { - logging.Errorf("Failed to initialize the MetricLogWriter: %+v", err) + logging.Error(err, "Failed to initialize the MetricLogWriter") return } @@ -76,7 +76,7 @@ func writeTaskLoop() { for _, t := range keys { err := metricWriter.Write(t, m[t]) if err != nil { - logging.Errorf("[MetricAggregatorTask] Write metric error: %+v", err) + logging.Error(err, "[MetricAggregatorTask] fail tp write metric") } } } diff --git a/core/log/metric/reader.go b/core/log/metric/reader.go index 2cd954daf..4f721640d 100644 --- a/core/log/metric/reader.go +++ b/core/log/metric/reader.go @@ -106,7 +106,7 @@ func (r *defaultMetricLogReader) readMetricsInOneFile(filename string, offset ui } item, err := base.MetricItemFromFatString(line) if err != nil { - logging.Errorf("Failed to convert MetricItem to string: %+v", err) + logging.Error(err, "Failed to convert MetricItem to string") continue } tsSec := item.Timestamp / 1000 @@ -140,7 +140,7 @@ func (r *defaultMetricLogReader) readMetricsInOneFileByEndTime(filename string, } item, err := base.MetricItemFromFatString(line) if err != nil { - logging.Errorf("Invalid line of metric file: %s, error: %+v", line, err) + logging.Error(err, "Invalid line of metric file", "fileLine", line) continue } tsSec := item.Timestamp / 1000 diff --git a/core/log/metric/searcher.go b/core/log/metric/searcher.go index f0bcbc129..5c849fcc8 100644 --- a/core/log/metric/searcher.go +++ b/core/log/metric/searcher.go @@ -56,7 +56,7 @@ func (s *DefaultMetricSearcher) searchOffsetAndRead(beginTimeMs uint64, doRead f // If cache is not up-to-date, we'll read from the initial position (offset 0 of the first file). offsetStart, fileNo, err := s.getOffsetStartAndFileIdx(filenames, beginTimeMs) if err != nil { - logging.Warnf("Failed to getOffsetStartAndFileIdx, beginTimeMs=%d, error: %+v", beginTimeMs, err) + logging.Warn("Failed to getOffsetStartAndFileIdx", "beginTimeMs", beginTimeMs, "err", err) } fileAmount := uint32(len(filenames)) for i := fileNo; i < fileAmount; i++ { @@ -65,8 +65,8 @@ func (s *DefaultMetricSearcher) searchOffsetAndRead(beginTimeMs uint64, doRead f // If offset = -1, it indicates that current file (i) does not satisfy the condition. offset, err := s.findOffsetToStart(filename, beginTimeMs, offsetStart) if err != nil { - logging.Warnf("Failed to findOffsetToStart, will try next file. Current beginTimeMs=%d, filename: %s, offsetStart: %d, err: %+v", - beginTimeMs, filename, offsetStart, err) + logging.Warn("Failed to findOffsetToStart, will try next file", "beginTimeMs", beginTimeMs, + "filename", filename, "offsetStart", offsetStart, "err", err) continue } if offset >= 0 { diff --git a/core/log/metric/writer.go b/core/log/metric/writer.go index ee2ccfa06..f598248b3 100644 --- a/core/log/metric/writer.go +++ b/core/log/metric/writer.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "os" + "path/filepath" "strconv" "strings" "sync" @@ -105,7 +106,7 @@ func (d *DefaultMetricLogWriter) writeItemsAndFlush(items []*base.MetricItem) er for _, item := range items { s, err := item.ToFatString() if err != nil { - logging.Warnf("Failed to convert MetricItem(resource=%s) to string: %+v", item.Resource, err) + logging.Warn("Failed to convert MetricItem to string", "resourceName", item.Resource, "err", err) continue } @@ -170,16 +171,16 @@ func (d *DefaultMetricLogWriter) removeDeprecatedFiles() error { idxFilename := formMetricIdxFileName(filename) err = os.Remove(filename) if err != nil { - logging.Errorf("[MetricWriter] Failed to remove metric log file <%s>: %+v", filename, err) + logging.Error(err, "[MetricWriter] Failed to remove metric log file", "filename", filename) } else { - logging.Infof("[MetricWriter] Metric log file removed: %s", filename) + logging.Info("[MetricWriter] Metric log file removed", "filename", filename) } err = os.Remove(idxFilename) if err != nil { - logging.Errorf("[MetricWriter] Failed to remove metric log file <%s>: %+v", idxFilename, err) + logging.Error(err, "[MetricWriter] Failed to remove metric log file", "idxFilename", idxFilename) } else { - logging.Infof("[MetricWriter] Metric index file removed: %s", idxFilename) + logging.Info("[MetricWriter] Metric index file removed", "idxFilename", idxFilename) } } return err @@ -195,7 +196,7 @@ func (d *DefaultMetricLogWriter) nextFileNameOfTime(time uint64) (string, error) return "", err } if len(list) == 0 { - return d.baseDir + filePattern, nil + return filepath.Join(d.baseDir, filePattern), nil } last := list[len(list)-1] var n uint32 = 0 @@ -206,7 +207,7 @@ func (d *DefaultMetricLogWriter) nextFileNameOfTime(time uint64) (string, error) n = uint32(v) } } - return fmt.Sprintf("%s%s.%d", d.baseDir, filePattern, n+1), nil + return filepath.Join(d.baseDir, fmt.Sprintf("%s.%d", filePattern, n+1)), nil } func (d *DefaultMetricLogWriter) closeCurAndNewFile(filename string) error { @@ -217,12 +218,12 @@ func (d *DefaultMetricLogWriter) closeCurAndNewFile(filename string) error { if d.curMetricFile != nil { if err = d.curMetricFile.Close(); err != nil { - logging.Errorf("[MetricWriter] Failed to close metric log file <%s>: %+v", d.curMetricFile.Name(), err) + logging.Error(err, "[MetricWriter] Failed to close metric log file", "curMetricFile", d.curMetricFile.Name()) } } if d.curMetricIdxFile != nil { if err = d.curMetricIdxFile.Close(); err != nil { - logging.Errorf("[MetricWriter] Failed to close metric index file <%s>: %+v", d.curMetricIdxFile.Name(), err) + logging.Error(err, "[MetricWriter] Failed to close metric index file", "curMetricIdxFile", d.curMetricIdxFile.Name()) } } // Create new metric log file, whether it exists or not. @@ -230,14 +231,14 @@ func (d *DefaultMetricLogWriter) closeCurAndNewFile(filename string) error { if err != nil { return err } - logging.Infof("[MetricWriter] New metric log file created: " + filename) + logging.Info("[MetricWriter] New metric log file created", "filename", filename) idxFile := formMetricIdxFileName(filename) mif, err := os.Create(idxFile) if err != nil { return err } - logging.Infof("[MetricWriter] New metric log index file created: " + idxFile) + logging.Info("[MetricWriter] New metric log index file created", "idxFile", idxFile) d.curMetricFile = mf d.metricOut = bufio.NewWriter(mf) @@ -282,7 +283,11 @@ func NewDefaultMetricLogWriterOfApp(maxSize uint64, maxFileAmount uint32, appNam } _, offset := time.Now().Zone() - baseDir := util.AddPathSeparatorIfAbsent(config.LogBaseDir()) + logDir := config.LogBaseDir() + if len(logDir) == 0 { + logDir = config.GetDefaultLogDir() + } + baseDir := logDir baseFilename := FormMetricFileName(appName, config.LogUsePid()) writer := &DefaultMetricLogWriter{ diff --git a/core/log/slot.go b/core/log/slot.go index 6d27deb09..572fb581e 100644 --- a/core/log/slot.go +++ b/core/log/slot.go @@ -4,17 +4,17 @@ import ( "github.com/alibaba/sentinel-golang/core/base" ) -type LogSlot struct { +type Slot struct { } -func (s *LogSlot) OnEntryPassed(_ *base.EntryContext) { +func (s *Slot) OnEntryPassed(_ *base.EntryContext) { } -func (s *LogSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { +func (s *Slot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { // TODO: write sentinel-block.log here } -func (s *LogSlot) OnCompleted(_ *base.EntryContext) { +func (s *Slot) OnCompleted(_ *base.EntryContext) { } diff --git a/core/stat/base/bucket_leap_array.go b/core/stat/base/bucket_leap_array.go index 63dfd2773..43ae27c1c 100644 --- a/core/stat/base/bucket_leap_array.go +++ b/core/stat/base/bucket_leap_array.go @@ -78,21 +78,21 @@ func (bla *BucketLeapArray) AddCount(event base.MetricEvent, count int64) { func (bla *BucketLeapArray) addCountWithTime(now uint64, event base.MetricEvent, count int64) { curBucket, err := bla.data.currentBucketOfTime(now, bla) if err != nil { - logging.Errorf("Failed to get current bucket, current ts=%d, err: %+v.", now, err) + logging.Error(err, "Failed to get current bucket", "now", now) return } if curBucket == nil { - logging.Error("Failed to add count: current bucket is nil") + logging.Error(errors.New("current bucket is nil"), "Failed to add count") return } mb := curBucket.Value.Load() if mb == nil { - logging.Error("Failed to add count: current bucket atomic Value is nil") + logging.Error(errors.New("nil bucket"), "Failed to add count: current bucket atomic Value is nil") return } b, ok := mb.(*MetricBucket) if !ok { - logging.Error("Failed to add count: bucket data type error") + logging.Error(errors.New("fail to type assert, expect MetricBucket"), "Failed to add count: bucket data type error") return } b.Add(event, count) @@ -107,18 +107,18 @@ func (bla *BucketLeapArray) Count(event base.MetricEvent) int64 { func (bla *BucketLeapArray) CountWithTime(now uint64, event base.MetricEvent) int64 { _, err := bla.data.currentBucketOfTime(now, bla) if err != nil { - logging.Errorf("Fail to get current bucket, err: %+v.", errors.WithStack(err)) + logging.Error(err, "Failed to get current bucket", "now", now) } count := int64(0) for _, ww := range bla.data.valuesWithTime(now) { mb := ww.Value.Load() if mb == nil { - logging.Error("Current bucket's Value is nil.") + logging.Error(errors.New("current bucket is nil"), "Failed to load current bucket") continue } b, ok := mb.(*MetricBucket) if !ok { - logging.Error("Fail to assert MetricBucket type.") + logging.Error(errors.New("fail to type assert, expect MetricBucket"), "fail to get current MetricBucket") continue } count += b.Get(event) @@ -130,7 +130,7 @@ func (bla *BucketLeapArray) CountWithTime(now uint64, event base.MetricEvent) in func (bla *BucketLeapArray) Values(now uint64) []*BucketWrap { _, err := bla.data.currentBucketOfTime(now, bla) if err != nil { - logging.Errorf("Fail to get current(%d) bucket, err: %+v.", now, err) + logging.Error(err, "Failed to get current bucket", "now", now) } return bla.data.valuesWithTime(now) } @@ -142,7 +142,7 @@ func (bla *BucketLeapArray) ValuesConditional(now uint64, predicate base.TimePre func (bla *BucketLeapArray) MinRt() int64 { _, err := bla.data.CurrentBucket(bla) if err != nil { - logging.Errorf("Fail to get current bucket, err: %+v.", err) + logging.Error(err, "Failed to get current bucket") } ret := base.DefaultStatisticMaxRt @@ -150,12 +150,12 @@ func (bla *BucketLeapArray) MinRt() int64 { for _, v := range bla.data.Values() { mb := v.Value.Load() if mb == nil { - logging.Error("Current bucket's Value is nil.") + logging.Error(errors.New("current bucket is nil"), "Failed to load current bucket") continue } b, ok := mb.(*MetricBucket) if !ok { - logging.Error("Fail to cast data as MetricBucket type") + logging.Error(errors.New("fail to type assert, expect MetricBucket"), "fail to get current MetricBucket") continue } mr := b.MinRt() diff --git a/core/stat/base/leap_array.go b/core/stat/base/leap_array.go index 0519d54f1..97195af73 100644 --- a/core/stat/base/leap_array.go +++ b/core/stat/base/leap_array.go @@ -155,7 +155,7 @@ func (la *LeapArray) CurrentBucket(bg BucketGenerator) (*BucketWrap, error) { } func (la *LeapArray) currentBucketOfTime(now uint64, bg BucketGenerator) (*BucketWrap, error) { - if now < 0 { + if now <= 0 { return nil, errors.New("Current time is less than 0.") } @@ -201,7 +201,7 @@ func (la *LeapArray) calculateTimeIdx(now uint64) int { return int(timeId) % la.array.length } -// Get all BucketWrap between [current time -1000ms, current time] +// Get all BucketWrap between [current time - leap array interval, current time] func (la *LeapArray) Values() []*BucketWrap { return la.valuesWithTime(util.CurrentTimeMillis()) } diff --git a/core/stat/base/leap_array_test.go b/core/stat/base/leap_array_test.go index db663c5b6..fff92a29a 100644 --- a/core/stat/base/leap_array_test.go +++ b/core/stat/base/leap_array_test.go @@ -28,7 +28,7 @@ func Test_bucketWrapper_Size(t *testing.T) { t.Errorf("the size of BucketWrap is not equal 24.\n") } if unsafe.Sizeof(ww) != 8 { - t.Errorf("the size of BucketWrap is not equal 24.\n") + t.Errorf("the size of BucketWrap pointer is not equal 8.\n") } } @@ -53,7 +53,6 @@ func Test_leapArray_calculateTimeIdx_normal(t *testing.T) { sampleCount uint32 intervalInMs uint32 array *AtomicBucketWrapArray - mux mutex } type args struct { timeMillis uint64 @@ -71,7 +70,6 @@ func Test_leapArray_calculateTimeIdx_normal(t *testing.T) { sampleCount: SampleCount, intervalInMs: IntervalInMs, array: NewAtomicBucketWrapArray(int(SampleCount), BucketLengthInMs, &leapArrayMock{}), - mux: mutex{}, }, args: args{ timeMillis: 1576296044907, @@ -86,7 +84,7 @@ func Test_leapArray_calculateTimeIdx_normal(t *testing.T) { sampleCount: tt.fields.sampleCount, intervalInMs: tt.fields.intervalInMs, array: tt.fields.array, - updateLock: tt.fields.mux, + updateLock: mutex{}, } if got := la.calculateTimeIdx(tt.args.timeMillis); got != tt.want { t.Errorf("LeapArray.calculateTimeIdx() = %v, want %v", got, tt.want) @@ -155,7 +153,6 @@ func Test_leapArray_valuesWithTime_normal(t *testing.T) { sampleCount uint32 intervalInMs uint32 array *AtomicBucketWrapArray - mux mutex } type args struct { timeMillis uint64 @@ -174,7 +171,6 @@ func Test_leapArray_valuesWithTime_normal(t *testing.T) { sampleCount: SampleCount, intervalInMs: IntervalInMs, array: NewAtomicBucketWrapArrayWithTime(int(SampleCount), BucketLengthInMs, uint64(1596199310000), &leapArrayMock{}), - mux: mutex{}, }, args: args{ timeMillis: 1576296049907, @@ -198,7 +194,7 @@ func Test_leapArray_valuesWithTime_normal(t *testing.T) { sampleCount: tt.fields.sampleCount, intervalInMs: tt.fields.intervalInMs, array: tt.fields.array, - updateLock: tt.fields.mux, + updateLock: mutex{}, } got := la.valuesWithTime(tt.args.timeMillis) for _, g := range got { @@ -224,7 +220,6 @@ func Test_leapArray_isBucketDeprecated_normal(t *testing.T) { sampleCount uint32 intervalInMs uint32 array *AtomicBucketWrapArray - mux mutex } type args struct { startTime uint64 @@ -243,7 +238,6 @@ func Test_leapArray_isBucketDeprecated_normal(t *testing.T) { sampleCount: SampleCount, intervalInMs: IntervalInMs, array: NewAtomicBucketWrapArrayWithTime(int(SampleCount), BucketLengthInMs, uint64(1596199310000), &leapArrayMock{}), - mux: mutex{}, }, args: args{ startTime: 1576296044907, @@ -262,7 +256,7 @@ func Test_leapArray_isBucketDeprecated_normal(t *testing.T) { sampleCount: tt.fields.sampleCount, intervalInMs: tt.fields.intervalInMs, array: tt.fields.array, - updateLock: tt.fields.mux, + updateLock: mutex{}, } if got := la.isBucketDeprecated(tt.args.startTime, tt.args.ww); got != tt.want { t.Errorf("LeapArray.isBucketDeprecated() = %v, want %v", got, tt.want) diff --git a/core/stat/base/metric_bucket_test.go b/core/stat/base/metric_bucket_test.go index e8f3d17ba..7c205cfa3 100644 --- a/core/stat/base/metric_bucket_test.go +++ b/core/stat/base/metric_bucket_test.go @@ -10,6 +10,7 @@ import ( func Test_metricBucket_MemSize(t *testing.T) { mb := NewMetricBucket() + t.Log("mb:", mb) size := unsafe.Sizeof(*mb) if size != 48 { t.Error("unexpect memory size of MetricBucket") diff --git a/core/stat/base/sliding_window_metric.go b/core/stat/base/sliding_window_metric.go index 5a856e863..f1a2b0c74 100644 --- a/core/stat/base/sliding_window_metric.go +++ b/core/stat/base/sliding_window_metric.go @@ -1,12 +1,13 @@ package base import ( - "fmt" + "reflect" "sync/atomic" "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/logging" "github.com/alibaba/sentinel-golang/util" + "github.com/pkg/errors" ) // SlidingWindowMetric represents the sliding window metric wrapper. @@ -21,37 +22,21 @@ type SlidingWindowMetric struct { } // It must pass the parameter point to the real storage entity -func NewSlidingWindowMetric(sampleCount, intervalInMs uint32, real *BucketLeapArray) *SlidingWindowMetric { - if real == nil || intervalInMs <= 0 || sampleCount <= 0 { - panic(fmt.Sprintf("Illegal parameters,intervalInMs=%d,sampleCount=%d,real=%+v.", intervalInMs, sampleCount, real)) +func NewSlidingWindowMetric(sampleCount, intervalInMs uint32, real *BucketLeapArray) (*SlidingWindowMetric, error) { + if real == nil { + return nil, errors.New("Nil BucketLeapArray") } - - if intervalInMs%sampleCount != 0 { - panic(fmt.Sprintf("Invalid parameters, intervalInMs is %d, sampleCount is %d.", intervalInMs, sampleCount)) + if err := base.CheckValidityForReuseStatistic(sampleCount, intervalInMs, real.SampleCount(), real.IntervalInMs()); err != nil { + return nil, err } bucketLengthInMs := intervalInMs / sampleCount - parentIntervalInMs := real.IntervalInMs() - parentBucketLengthInMs := real.BucketLengthInMs() - - // bucketLengthInMs of BucketLeapArray must be divisible by bucketLengthInMs of SlidingWindowMetric - // for example: bucketLengthInMs of BucketLeapArray is 500ms, and bucketLengthInMs of SlidingWindowMetric is 2000ms - // for example: bucketLengthInMs of BucketLeapArray is 500ms, and bucketLengthInMs of SlidingWindowMetric is 500ms - if bucketLengthInMs%parentBucketLengthInMs != 0 { - panic(fmt.Sprintf("BucketLeapArray's BucketLengthInMs(%d) is not divisible by SlidingWindowMetric's BucketLengthInMs(%d).", parentBucketLengthInMs, bucketLengthInMs)) - } - - if intervalInMs > parentIntervalInMs { - // todo if SlidingWindowMetric's intervalInMs is greater than BucketLeapArray. - panic(fmt.Sprintf("The interval(%d) of SlidingWindowMetric is greater than parent BucketLeapArray(%d).", intervalInMs, parentIntervalInMs)) - } - return &SlidingWindowMetric{ bucketLengthInMs: bucketLengthInMs, sampleCount: sampleCount, intervalInMs: intervalInMs, real: real, - } + }, nil } // Get the start time range of the bucket for the provided time. @@ -72,12 +57,12 @@ func (m *SlidingWindowMetric) count(event base.MetricEvent, values []*BucketWrap for _, ww := range values { mb := ww.Value.Load() if mb == nil { - logging.Error("Illegal state: current bucket Value is nil when summing count") + logging.Error(errors.New("nil BucketWrap"), "Illegal state: current bucket Value is nil when summing count") continue } counter, ok := mb.(*MetricBucket) if !ok { - logging.Errorf("Fail to cast data Value(%+v) to MetricBucket type", mb) + logging.Error(errors.New("type assert failed"), "Fail to do type assert, expect: MetricBucket", "type", reflect.TypeOf(mb).Name()) continue } ret += counter.Get(event) @@ -119,12 +104,12 @@ func (m *SlidingWindowMetric) GetMaxOfSingleBucket(event base.MetricEvent) int64 for _, w := range satisfiedBuckets { mb := w.Value.Load() if mb == nil { - logging.Error("Illegal state: current bucket Value is nil when GetMaxOfSingleBucket") + logging.Error(errors.New("nil BucketWrap"), "Illegal state: current bucket Value is nil when GetMaxOfSingleBucket") continue } counter, ok := mb.(*MetricBucket) if !ok { - logging.Errorf("Failed to cast data Value(%+v) to MetricBucket type", mb) + logging.Error(errors.New("type assert failed"), "Fail to do type assert, expect: MetricBucket", "type", reflect.TypeOf(mb).Name()) continue } v := counter.Get(event) @@ -145,12 +130,12 @@ func (m *SlidingWindowMetric) MinRT() float64 { for _, w := range satisfiedBuckets { mb := w.Value.Load() if mb == nil { - logging.Error("Illegal state: current bucket Value is nil when calculating minRT") + logging.Error(errors.New("nil BucketWrap"), "Illegal state: current bucket Value is nil when calculating minRT") continue } counter, ok := mb.(*MetricBucket) if !ok { - logging.Errorf("Failed to cast data Value(%+v) to MetricBucket type", mb) + logging.Error(errors.New("type assert failed"), "Fail to do type assert, expect: MetricBucket", "type", reflect.TypeOf(mb).Name()) continue } v := counter.MinRt() @@ -204,12 +189,12 @@ func (m *SlidingWindowMetric) metricItemFromBuckets(ts uint64, ws []*BucketWrap) for _, w := range ws { mi := w.Value.Load() if mi == nil { - logging.Error("Get nil bucket when generating MetricItem from buckets") + logging.Error(errors.New("nil BucketWrap"), "Get nil bucket when generating MetricItem from buckets") return nil } mb, ok := mi.(*MetricBucket) if !ok { - logging.Errorf("Failed to cast to MetricBucket type, bucket startTime: %d", w.BucketStart) + logging.Error(errors.New("type assert failed"), "Fail to do type assert, expect: MetricBucket", "bucketStartTime", w.BucketStart, "type", reflect.TypeOf(mb).Name()) return nil } item.PassQps += uint64(mb.Get(base.MetricEventPass)) @@ -229,12 +214,12 @@ func (m *SlidingWindowMetric) metricItemFromBuckets(ts uint64, ws []*BucketWrap) func (m *SlidingWindowMetric) metricItemFromBucket(w *BucketWrap) *base.MetricItem { mi := w.Value.Load() if mi == nil { - logging.Error("Get nil bucket when generating MetricItem from buckets") + logging.Error(errors.New("nil BucketWrap"), "Get nil bucket when generating MetricItem from buckets") return nil } mb, ok := mi.(*MetricBucket) if !ok { - logging.Errorf("Fail to cast data Value to MetricBucket type, bucket startTime: %d", w.BucketStart) + logging.Error(errors.New("type assert failed"), "Fail to do type assert, expect: MetricBucket", "type", reflect.TypeOf(mb).Name()) return nil } completeQps := mb.Get(base.MetricEventComplete) diff --git a/core/stat/base/sliding_window_metric_test.go b/core/stat/base/sliding_window_metric_test.go index 7767e9537..ca2c15e80 100644 --- a/core/stat/base/sliding_window_metric_test.go +++ b/core/stat/base/sliding_window_metric_test.go @@ -1,11 +1,10 @@ package base import ( - "reflect" - "strings" "testing" "github.com/alibaba/sentinel-golang/core/base" + "github.com/stretchr/testify/assert" ) func TestSlidingWindowMetric_getBucketStartRange(t *testing.T) { @@ -95,7 +94,9 @@ func TestSlidingWindowMetric_getBucketStartRange(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - m := NewSlidingWindowMetric(tt.args.sampleCount, tt.args.intervalInMs, NewBucketLeapArray(tt.args.realSampleCount, tt.args.realIntervalInMs)) + m, err := NewSlidingWindowMetric(tt.args.sampleCount, tt.args.intervalInMs, NewBucketLeapArray(tt.args.realSampleCount, tt.args.realIntervalInMs)) + assert.True(t, err == nil) + gotStart, gotEnd := m.getBucketStartRange(tt.args.now) if gotStart != tt.wantStart { t.Errorf("SlidingWindowMetric.getBucketStartRange() gotStart = %v, want %v", gotStart, tt.wantStart) @@ -108,81 +109,16 @@ func TestSlidingWindowMetric_getBucketStartRange(t *testing.T) { } func Test_NewSlidingWindowMetric(t *testing.T) { - type args struct { - sampleCount uint32 - intervalInMs uint32 - real *BucketLeapArray - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Test_NewSlidingWindowMetric-1", - args: args{ - intervalInMs: 2000, - sampleCount: 4, - real: NewBucketLeapArray(SampleCount, IntervalInMs), - }, - want: "", - }, - { - name: "Test_NewSlidingWindowMetric-2", - args: args{ - intervalInMs: 0, - sampleCount: 0, - real: NewBucketLeapArray(SampleCount, IntervalInMs), - }, - want: "Illegal parameters,intervalInMs=0,sampleCount=0,real=", - }, - { - name: "Test_NewSlidingWindowMetric-3", - args: args{ - intervalInMs: 2001, - sampleCount: 4, - real: NewBucketLeapArray(SampleCount, IntervalInMs), - }, - want: "Invalid parameters, intervalInMs is 2001, sampleCount is 4.", - }, - { - name: "Test_NewSlidingWindowMetric-4", - args: args{ - intervalInMs: 2002, - sampleCount: 2, - real: NewBucketLeapArray(SampleCount, IntervalInMs), - }, - want: "BucketLeapArray's BucketLengthInMs(500) is not divisible by SlidingWindowMetric's BucketLengthInMs(1001).", - }, - { - name: "Test_NewSlidingWindowMetric-5", - args: args{ - intervalInMs: 200000, - sampleCount: 4, - real: NewBucketLeapArray(SampleCount, IntervalInMs), - }, - want: "The interval(200000) of SlidingWindowMetric is greater than parent BucketLeapArray(10000).", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - if err := recover(); err != nil { - errContent, ok := err.(string) - if !ok { - t.Errorf("Fail to assert err, except string, in fact:%+v", reflect.TypeOf(err)) - } - if !strings.Contains(errContent, tt.want) { - t.Errorf("Failed, except [%s],in fact:[%s]", tt.want, errContent) - } - } - }() - got := NewSlidingWindowMetric(tt.args.sampleCount, tt.args.intervalInMs, tt.args.real) - if got == nil || "" != tt.want { - t.Errorf("NewSlidingWindowMetric() = %v", got) - } - }) - } + got, err := NewSlidingWindowMetric(4, 2000, NewBucketLeapArray(SampleCount, IntervalInMs)) + assert.True(t, err == nil && got != nil) + got, err = NewSlidingWindowMetric(0, 0, NewBucketLeapArray(SampleCount, IntervalInMs)) + assert.True(t, got == nil && err != nil) + got, err = NewSlidingWindowMetric(4, 2001, NewBucketLeapArray(SampleCount, IntervalInMs)) + assert.True(t, got == nil && err != nil) + got, err = NewSlidingWindowMetric(2, 2002, NewBucketLeapArray(SampleCount, IntervalInMs)) + assert.True(t, got == nil && err != nil) + got, err = NewSlidingWindowMetric(4, 200000, NewBucketLeapArray(SampleCount, IntervalInMs)) + assert.True(t, got == nil && err != nil) } func TestSlidingWindowMetric_GetIntervalSumWithTime(t *testing.T) { @@ -223,7 +159,7 @@ func TestSlidingWindowMetric_GetIntervalSumWithTime(t *testing.T) { for i := 0; i < int(tt.fields.intervalInMs); i++ { tt.fields.real.addCountWithTime(tt.args.now-100-uint64(i), tt.args.event, 1) } - m := NewSlidingWindowMetric(tt.fields.sampleCount, tt.fields.intervalInMs, tt.fields.real) + m, _ := NewSlidingWindowMetric(tt.fields.sampleCount, tt.fields.intervalInMs, tt.fields.real) if got := m.getSumWithTime(tt.args.now, tt.args.event); got != tt.want { t.Errorf("SlidingWindowMetric.getSumWithTime() = %v, want %v", got, tt.want) } diff --git a/core/stat/base_node.go b/core/stat/base_node.go index 43b98ee0a..637ef1cee 100644 --- a/core/stat/base_node.go +++ b/core/stat/base_node.go @@ -4,6 +4,7 @@ import ( "sync/atomic" "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/core/config" sbase "github.com/alibaba/sentinel-golang/core/stat/base" ) @@ -18,8 +19,8 @@ type BaseStatNode struct { } func NewBaseStatNode(sampleCount uint32, intervalInMs uint32) *BaseStatNode { - la := sbase.NewBucketLeapArray(base.DefaultSampleCountTotal, base.DefaultIntervalMsTotal) - metric := sbase.NewSlidingWindowMetric(sampleCount, intervalInMs, la) + la := sbase.NewBucketLeapArray(config.GlobalStatisticSampleCountTotal(), config.GlobalStatisticIntervalMsTotal()) + metric, _ := sbase.NewSlidingWindowMetric(sampleCount, intervalInMs, la) return &BaseStatNode{ goroutineNum: 0, sampleCount: sampleCount, @@ -49,7 +50,7 @@ func (n *BaseStatNode) GetMaxAvg(event base.MetricEvent) float64 { return float64(n.metric.GetMaxOfSingleBucket(event)) * float64(n.sampleCount) / float64(n.intervalMs) * 1000 } -func (n *BaseStatNode) AddMetric(event base.MetricEvent, count uint64) { +func (n *BaseStatNode) AddCount(event base.MetricEvent, count int64) { n.arr.AddCount(event, int64(count)) } @@ -81,3 +82,11 @@ func (n *BaseStatNode) Reset() { // TODO: this should be thread-safe, or error may occur panic("to be implemented") } + +func (n *BaseStatNode) GenerateReadStat(sampleCount uint32, intervalInMs uint32) (base.ReadStat, error) { + return sbase.NewSlidingWindowMetric(sampleCount, intervalInMs, n.arr) +} + +func (n *BaseStatNode) DefaultMetric() base.ReadStat { + return n.metric +} diff --git a/core/stat/node_storage.go b/core/stat/node_storage.go index eab20f2e1..f473affd4 100644 --- a/core/stat/node_storage.go +++ b/core/stat/node_storage.go @@ -54,7 +54,7 @@ func GetOrCreateResourceNode(resource string, resourceType base.ResourceType) *R } if len(resNodeMap) >= int(base.DefaultMaxResourceAmount) { - logging.Warnf("Resource amount exceeds the threshold: %d.", base.DefaultMaxResourceAmount) + logging.Warn("Resource amount exceeds the threshold", "maxResourceAmount", base.DefaultMaxResourceAmount) } node = NewResourceNode(resource, resourceType) resNodeMap[resource] = node diff --git a/core/stat/resource_node.go b/core/stat/resource_node.go index 5cb9a5903..261a3126e 100644 --- a/core/stat/resource_node.go +++ b/core/stat/resource_node.go @@ -1,11 +1,8 @@ package stat import ( - "fmt" - "sync" - "github.com/alibaba/sentinel-golang/core/base" - sbase "github.com/alibaba/sentinel-golang/core/stat/base" + "github.com/alibaba/sentinel-golang/core/config" ) type ResourceNode struct { @@ -13,19 +10,14 @@ type ResourceNode struct { resourceName string resourceType base.ResourceType - // key is "sampleCount/intervalInMs" - readOnlyStats map[string]*sbase.SlidingWindowMetric - updateLock sync.RWMutex } // NewResourceNode creates a new resource node with given name and classification. func NewResourceNode(resourceName string, resourceType base.ResourceType) *ResourceNode { return &ResourceNode{ - // TODO: make this configurable - BaseStatNode: *NewBaseStatNode(base.DefaultSampleCount, base.DefaultIntervalMs), - resourceName: resourceName, - resourceType: resourceType, - readOnlyStats: make(map[string]*sbase.SlidingWindowMetric), + BaseStatNode: *NewBaseStatNode(config.MetricStatisticSampleCount(), config.MetricStatisticIntervalMs()), + resourceName: resourceName, + resourceType: resourceType, } } @@ -36,30 +28,3 @@ func (n *ResourceNode) ResourceType() base.ResourceType { func (n *ResourceNode) ResourceName() string { return n.resourceName } - -func (n *ResourceNode) GetSlidingWindowMetric(key string) *sbase.SlidingWindowMetric { - n.updateLock.RLock() - defer n.updateLock.RUnlock() - return n.readOnlyStats[key] -} - -func (n *ResourceNode) GetOrCreateSlidingWindowMetric(sampleCount, intervalInMs uint32) *sbase.SlidingWindowMetric { - key := fmt.Sprintf("%d/%d", sampleCount, intervalInMs) - fastVal := n.GetSlidingWindowMetric(key) - if fastVal != nil { - return fastVal - } - - n.updateLock.Lock() - defer n.updateLock.Unlock() - - v, exist := n.readOnlyStats[key] - if exist { - return v - } - - newSlidingWindow := sbase.NewSlidingWindowMetric(sampleCount, intervalInMs, n.arr) - n.readOnlyStats[key] = newSlidingWindow - // TODO clean unused entity in readOnlyStats. - return newSlidingWindow -} diff --git a/core/stat/resource_node_test.go b/core/stat/resource_node_test.go deleted file mode 100644 index ae43840ef..000000000 --- a/core/stat/resource_node_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package stat - -import ( - "fmt" - "sync" - "testing" - - "github.com/alibaba/sentinel-golang/core/base" - "github.com/stretchr/testify/assert" -) - -func TestResourceNode_GetOrCreateSlidingWindowMetric(t *testing.T) { - type args struct { - sampleCount uint32 - intervalInMs uint32 - } - tests := []struct { - name string - }{ - { - name: "TestResourceNode_GetOrCreateSlidingWindowMetric", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - n := NewResourceNode("aa", base.ResTypeCommon) - - argsList := []args{ - { - sampleCount: 10, - intervalInMs: 10000, - }, - { - sampleCount: 5, - intervalInMs: 10000, - }, - { - sampleCount: 2, - intervalInMs: 10000, - }, - { - sampleCount: 1, - intervalInMs: 10000, - }, - { - sampleCount: 10, - intervalInMs: 5000, - }, - { - sampleCount: 2, - intervalInMs: 5000, - }, - { - sampleCount: 5, - intervalInMs: 5000, - }, - { - sampleCount: 1, - intervalInMs: 5000, - }, - { - sampleCount: 1, - intervalInMs: 2000, - }, - { - sampleCount: 2, - intervalInMs: 2000, - }, - } - - wg := &sync.WaitGroup{} - wg.Add(100) - for i := 0; i < 100; i++ { - go func(g *sync.WaitGroup) { - for _, as := range argsList { - n.GetOrCreateSlidingWindowMetric(as.sampleCount, as.intervalInMs) - } - g.Done() - }(wg) - } - wg.Wait() - - for _, as := range argsList { - key := fmt.Sprintf("%d/%d", as.sampleCount, as.intervalInMs) - assert.True(t, n.GetSlidingWindowMetric(key) != nil) - } - assert.True(t, len(n.readOnlyStats) == 10) - }) - } -} diff --git a/core/stat/stat_prepare_slot.go b/core/stat/stat_prepare_slot.go index 092e88530..67fc873f0 100644 --- a/core/stat/stat_prepare_slot.go +++ b/core/stat/stat_prepare_slot.go @@ -4,10 +4,10 @@ import ( "github.com/alibaba/sentinel-golang/core/base" ) -type StatNodePrepareSlot struct { +type ResourceNodePrepareSlot struct { } -func (s *StatNodePrepareSlot) Prepare(ctx *base.EntryContext) { +func (s *ResourceNodePrepareSlot) Prepare(ctx *base.EntryContext) { node := GetOrCreateResourceNode(ctx.Resource.Name(), ctx.Resource.Classification()) // Set the resource node to the context. ctx.StatNode = node diff --git a/core/stat/stat_slot.go b/core/stat/stat_slot.go index 23d083c89..d02e3c26c 100644 --- a/core/stat/stat_slot.go +++ b/core/stat/stat_slot.go @@ -5,61 +5,55 @@ import ( "github.com/alibaba/sentinel-golang/util" ) -const SlotName = "StatisticSlot" - -type StatisticSlot struct { -} - -func (s *StatisticSlot) String() string { - return SlotName +type Slot struct { } -func (s *StatisticSlot) OnEntryPassed(ctx *base.EntryContext) { - s.recordPassFor(ctx.StatNode, ctx.Input.AcquireCount) +func (s *Slot) OnEntryPassed(ctx *base.EntryContext) { + s.recordPassFor(ctx.StatNode, ctx.Input.BatchCount) if ctx.Resource.FlowType() == base.Inbound { - s.recordPassFor(InboundNode(), ctx.Input.AcquireCount) + s.recordPassFor(InboundNode(), ctx.Input.BatchCount) } } -func (s *StatisticSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { - s.recordBlockFor(ctx.StatNode, ctx.Input.AcquireCount) +func (s *Slot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { + s.recordBlockFor(ctx.StatNode, ctx.Input.BatchCount) if ctx.Resource.FlowType() == base.Inbound { - s.recordBlockFor(InboundNode(), ctx.Input.AcquireCount) + s.recordBlockFor(InboundNode(), ctx.Input.BatchCount) } } -func (s *StatisticSlot) OnCompleted(ctx *base.EntryContext) { +func (s *Slot) OnCompleted(ctx *base.EntryContext) { rt := util.CurrentTimeMillis() - ctx.StartTime() ctx.PutRt(rt) - s.recordCompleteFor(ctx.StatNode, ctx.Input.AcquireCount, rt, ctx.Err()) + s.recordCompleteFor(ctx.StatNode, ctx.Input.BatchCount, rt, ctx.Err()) if ctx.Resource.FlowType() == base.Inbound { - s.recordCompleteFor(InboundNode(), ctx.Input.AcquireCount, rt, ctx.Err()) + s.recordCompleteFor(InboundNode(), ctx.Input.BatchCount, rt, ctx.Err()) } } -func (s *StatisticSlot) recordPassFor(sn base.StatNode, count uint32) { +func (s *Slot) recordPassFor(sn base.StatNode, count uint32) { if sn == nil { return } sn.IncreaseGoroutineNum() - sn.AddMetric(base.MetricEventPass, uint64(count)) + sn.AddCount(base.MetricEventPass, int64(count)) } -func (s *StatisticSlot) recordBlockFor(sn base.StatNode, count uint32) { +func (s *Slot) recordBlockFor(sn base.StatNode, count uint32) { if sn == nil { return } - sn.AddMetric(base.MetricEventBlock, uint64(count)) + sn.AddCount(base.MetricEventBlock, int64(count)) } -func (s *StatisticSlot) recordCompleteFor(sn base.StatNode, count uint32, rt uint64, err error) { +func (s *Slot) recordCompleteFor(sn base.StatNode, count uint32, rt uint64, err error) { if sn == nil { return } if err != nil { - sn.AddMetric(base.MetricEventError, uint64(count)) + sn.AddCount(base.MetricEventError, int64(count)) } - sn.AddMetric(base.MetricEventRt, rt) - sn.AddMetric(base.MetricEventComplete, uint64(count)) + sn.AddCount(base.MetricEventRt, int64(rt)) + sn.AddCount(base.MetricEventComplete, int64(count)) sn.DecreaseGoroutineNum() } diff --git a/core/system/rule.go b/core/system/rule.go index b5da612a7..bbca9af8a 100644 --- a/core/system/rule.go +++ b/core/system/rule.go @@ -56,24 +56,23 @@ func (t AdaptiveStrategy) String() string { } } -type SystemRule struct { - ID uint64 `json:"id,omitempty"` - +type Rule struct { + ID string `json:"id,omitempty"` MetricType MetricType `json:"metricType"` TriggerCount float64 `json:"triggerCount"` Strategy AdaptiveStrategy `json:"strategy"` } -func (r *SystemRule) String() string { +func (r *Rule) String() string { b, err := json.Marshal(r) if err != nil { // Return the fallback string - return fmt.Sprintf("SystemRule{metricType=%s, triggerCount=%.2f, adaptiveStrategy=%s}", + return fmt.Sprintf("Rule{metricType=%s, triggerCount=%.2f, adaptiveStrategy=%s}", r.MetricType, r.TriggerCount, r.Strategy) } return string(b) } -func (r *SystemRule) ResourceName() string { +func (r *Rule) ResourceName() string { return r.MetricType.String() } diff --git a/core/system/rule_manager.go b/core/system/rule_manager.go index 2d6d95d59..b26f415e1 100644 --- a/core/system/rule_manager.go +++ b/core/system/rule_manager.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" ) -type RuleMap map[MetricType][]*SystemRule +type RuleMap map[MetricType][]*Rule // const var ( @@ -16,12 +16,32 @@ var ( ruleMapMux = new(sync.RWMutex) ) -// GetRules return all the rules -func GetRules() []*SystemRule { +// GetRules returns all the rules based on copy. +// It doesn't take effect for system module if user changes the rule. +// GetRules need to compete system module's global lock and the high performance losses of copy, +// reduce or do not call GetRules if possible +func GetRules() []Rule { + rules := make([]*Rule, 0, len(ruleMap)) + ruleMapMux.RLock() + for _, rs := range ruleMap { + rules = append(rules, rs...) + } + ruleMapMux.RUnlock() + + ret := make([]Rule, 0, len(rules)) + for _, r := range rules { + ret = append(ret, *r) + } + return ret +} + +// getRules returns all the rules。Any changes of rules take effect for system module +// getRules is an internal interface. +func getRules() []*Rule { ruleMapMux.RLock() defer ruleMapMux.RUnlock() - rules := make([]*SystemRule, 0) + rules := make([]*Rule, 0) for _, rs := range ruleMap { rules = append(rules, rs...) } @@ -29,11 +49,11 @@ func GetRules() []*SystemRule { } // LoadRules loads given system rules to the rule manager, while all previous rules will be replaced. -func LoadRules(rules []*SystemRule) (bool, error) { +func LoadRules(rules []*Rule) (bool, error) { m := buildRuleMap(rules) if err := onRuleUpdate(m); err != nil { - logging.Errorf("Fail to load rules %+v, err: %+v", rules, err) + logging.Error(err, "Fail to load rules", "rules", rules) return false, err } @@ -51,9 +71,9 @@ func onRuleUpdate(r RuleMap) error { ruleMapMux.Lock() defer func() { ruleMapMux.Unlock() - logging.Debugf("Updating system rule spends %d ns.", util.CurrentTimeNano()-start) + logging.Debug("time statistic(ns) for updating system rule", "timeCost", util.CurrentTimeNano()-start) if len(r) > 0 { - logging.Infof("[SystemRuleManager] System rules loaded: %v", r) + logging.Info("[SystemRuleManager] System rules loaded", "rules", r) } else { logging.Info("[SystemRuleManager] System rules were cleared") } @@ -62,7 +82,7 @@ func onRuleUpdate(r RuleMap) error { return nil } -func buildRuleMap(rules []*SystemRule) RuleMap { +func buildRuleMap(rules []*Rule) RuleMap { m := make(RuleMap) if len(rules) == 0 { @@ -71,12 +91,12 @@ func buildRuleMap(rules []*SystemRule) RuleMap { for _, rule := range rules { if err := IsValidSystemRule(rule); err != nil { - logging.Warnf("Ignoring invalid system rule: %v, reason: %s", rule, err.Error()) + logging.Warn("Ignoring invalid system rule", "rule", rule, "err", err) continue } rulesOfRes, exists := m[rule.MetricType] if !exists { - m[rule.MetricType] = []*SystemRule{rule} + m[rule.MetricType] = []*Rule{rule} } else { m[rule.MetricType] = append(rulesOfRes, rule) } @@ -85,9 +105,9 @@ func buildRuleMap(rules []*SystemRule) RuleMap { } // IsValidSystemRule determine the system rule is valid or not -func IsValidSystemRule(rule *SystemRule) error { +func IsValidSystemRule(rule *Rule) error { if rule == nil { - return errors.New("nil SystemRule") + return errors.New("nil Rule") } if rule.TriggerCount < 0 { return errors.New("negative threshold") diff --git a/core/system/rule_manager_test.go b/core/system/rule_manager_test.go index 3b8e5303a..773725e29 100644 --- a/core/system/rule_manager_test.go +++ b/core/system/rule_manager_test.go @@ -8,24 +8,24 @@ import ( func TestGetRules(t *testing.T) { t.Run("EmptyRules", func(t *testing.T) { - rules := GetRules() + rules := getRules() assert.Equal(t, 0, len(rules)) }) t.Run("GetUpdatedRules", func(t *testing.T) { defer func() { ruleMap = make(RuleMap) }() - r := map[MetricType][]*SystemRule{ - InboundQPS: {&SystemRule{MetricType: InboundQPS, TriggerCount: 1}}, - Concurrency: {&SystemRule{MetricType: Concurrency, TriggerCount: 2}}, + r := map[MetricType][]*Rule{ + InboundQPS: {&Rule{MetricType: InboundQPS, TriggerCount: 1}}, + Concurrency: {&Rule{MetricType: Concurrency, TriggerCount: 2}}, } ruleMap = r - rules := GetRules() + rules := getRules() assert.Equal(t, 2, len(rules)) - r[InboundQPS] = append(r[InboundQPS], &SystemRule{MetricType: InboundQPS, TriggerCount: 2}) + r[InboundQPS] = append(r[InboundQPS], &Rule{MetricType: InboundQPS, TriggerCount: 2}) ruleMap = r - rules = GetRules() + rules = getRules() assert.Equal(t, 3, len(rules)) }) } @@ -40,7 +40,7 @@ func TestLoadRules(t *testing.T) { t.Run("ValidSystemRule", func(t *testing.T) { defer func() { ruleMap = make(RuleMap) }() - sRule := []*SystemRule{ + sRule := []*Rule{ {MetricType: InboundQPS, TriggerCount: 1}, {MetricType: Concurrency, TriggerCount: 2}, } @@ -59,9 +59,9 @@ func TestClearRules(t *testing.T) { }) t.Run("NoEmptyOriginRuleMap", func(t *testing.T) { - r := map[MetricType][]*SystemRule{ - InboundQPS: {&SystemRule{MetricType: InboundQPS, TriggerCount: 1}}, - Concurrency: {&SystemRule{MetricType: Concurrency, TriggerCount: 2}}, + r := map[MetricType][]*Rule{ + InboundQPS: {&Rule{MetricType: InboundQPS, TriggerCount: 1}}, + Concurrency: {&Rule{MetricType: Concurrency, TriggerCount: 2}}, } ruleMap = r err := ClearRules() @@ -80,10 +80,10 @@ func TestOnRuleUpdate(t *testing.T) { t.Run("ValidSystemRule", func(t *testing.T) { defer func() { ruleMap = make(RuleMap) }() rMap := RuleMap{ - InboundQPS: []*SystemRule{ + InboundQPS: []*Rule{ {MetricType: InboundQPS, TriggerCount: 1}, }, - Concurrency: []*SystemRule{ + Concurrency: []*Rule{ {MetricType: Concurrency, TriggerCount: 2}, }, } @@ -100,7 +100,7 @@ func TestBuildRuleMap(t *testing.T) { }) t.Run("InvalidSystemRule", func(t *testing.T) { - sRule := []*SystemRule{ + sRule := []*Rule{ {MetricType: InboundQPS, TriggerCount: -1}, } r := buildRuleMap(sRule) @@ -108,7 +108,7 @@ func TestBuildRuleMap(t *testing.T) { }) t.Run("ValidSystemRule", func(t *testing.T) { - sRule := []*SystemRule{ + sRule := []*Rule{ {MetricType: InboundQPS, TriggerCount: 1}, {MetricType: Concurrency, TriggerCount: 2}, } @@ -117,7 +117,7 @@ func TestBuildRuleMap(t *testing.T) { }) t.Run("MultiRuleOneTypeValidSystemRule", func(t *testing.T) { - sRule := []*SystemRule{ + sRule := []*Rule{ {MetricType: InboundQPS, TriggerCount: 1}, {MetricType: InboundQPS, TriggerCount: 2}, } @@ -129,29 +129,29 @@ func TestBuildRuleMap(t *testing.T) { func TestIsValidSystemRule(t *testing.T) { t.Run("NilSystemRule", func(t *testing.T) { err := IsValidSystemRule(nil) - assert.EqualError(t, err, "nil SystemRule") + assert.EqualError(t, err, "nil Rule") }) t.Run("NegativeThreshold", func(t *testing.T) { - sRule := &SystemRule{MetricType: InboundQPS, TriggerCount: -1} + sRule := &Rule{MetricType: InboundQPS, TriggerCount: -1} err := IsValidSystemRule(sRule) assert.EqualError(t, err, "negative threshold") }) t.Run("InvalidMetricType", func(t *testing.T) { - sRule := &SystemRule{MetricType: MetricTypeSize} + sRule := &Rule{MetricType: MetricTypeSize} err := IsValidSystemRule(sRule) assert.EqualError(t, err, "invalid metric type") }) t.Run("InvalidCPUUsage", func(t *testing.T) { - sRule := &SystemRule{MetricType: CpuUsage, TriggerCount: 75} + sRule := &Rule{MetricType: CpuUsage, TriggerCount: 75} err := IsValidSystemRule(sRule) assert.EqualError(t, err, "invalid CPU usage, valid range is [0.0, 1.0]") }) t.Run("ValidSystemRule", func(t *testing.T) { - sRule := &SystemRule{MetricType: Load, TriggerCount: 12, Strategy: BBR} + sRule := &Rule{MetricType: Load, TriggerCount: 12, Strategy: BBR} err := IsValidSystemRule(sRule) assert.NoError(t, err) }) diff --git a/core/system/rule_test.go b/core/system/rule_test.go index 67a6f72c2..2923c5689 100644 --- a/core/system/rule_test.go +++ b/core/system/rule_test.go @@ -57,14 +57,14 @@ func TestAdaptiveStrategyString(t *testing.T) { func TestSystemRuleResourceName(t *testing.T) { t.Run("ValidResourceName", func(t *testing.T) { - sr := &SystemRule{MetricType: Concurrency} + sr := &Rule{MetricType: Concurrency} assert.Equal(t, "concurrency", sr.ResourceName()) }) } func TestSystemRuleString(t *testing.T) { t.Run("ValidSystemRuleString", func(t *testing.T) { - sr := &SystemRule{MetricType: Concurrency} - assert.NotContains(t, sr.String(), "SystemRule") + sr := &Rule{MetricType: Concurrency} + assert.NotContains(t, sr.String(), "Rule") }) } diff --git a/core/system/slot.go b/core/system/slot.go index 3198219df..891989af8 100644 --- a/core/system/slot.go +++ b/core/system/slot.go @@ -5,16 +5,14 @@ import ( "github.com/alibaba/sentinel-golang/core/stat" ) -const SlotName = "SystemAdaptiveSlot" - -type SystemAdaptiveSlot struct { +type AdaptiveSlot struct { } -func (s *SystemAdaptiveSlot) Check(ctx *base.EntryContext) *base.TokenResult { +func (s *AdaptiveSlot) Check(ctx *base.EntryContext) *base.TokenResult { if ctx == nil || ctx.Resource == nil || ctx.Resource.FlowType() != base.Inbound { return nil } - rules := GetRules() + rules := getRules() result := ctx.RuleCheckResult for _, rule := range rules { passed, snapshotValue := s.doCheckRule(rule) @@ -31,7 +29,7 @@ func (s *SystemAdaptiveSlot) Check(ctx *base.EntryContext) *base.TokenResult { return result } -func (s *SystemAdaptiveSlot) doCheckRule(rule *SystemRule) (bool, float64) { +func (s *AdaptiveSlot) doCheckRule(rule *Rule) (bool, float64) { threshold := rule.TriggerCount switch rule.MetricType { case InboundQPS: @@ -76,7 +74,3 @@ func checkBbrSimple() bool { } return true } - -func (s *SystemAdaptiveSlot) String() string { - return SlotName -} diff --git a/core/system/slot_test.go b/core/system/slot_test.go index 0da5d0509..287a72c9f 100644 --- a/core/system/slot_test.go +++ b/core/system/slot_test.go @@ -9,7 +9,7 @@ import ( ) func TestCheckNilInput(t *testing.T) { - var sas *SystemAdaptiveSlot + var sas *AdaptiveSlot t.Run("NilInput", func(t *testing.T) { r := sas.Check(nil) @@ -29,7 +29,7 @@ func TestCheckNilInput(t *testing.T) { } func TestCheckEmptyRule(t *testing.T) { - var sas *SystemAdaptiveSlot + var sas *AdaptiveSlot rw := base.NewResourceWrapper("test", base.ResTypeCommon, base.Inbound) r := sas.Check(&base.EntryContext{ Resource: rw, @@ -39,8 +39,8 @@ func TestCheckEmptyRule(t *testing.T) { } func TestDoCheckRuleConcurrency(t *testing.T) { - var sas *SystemAdaptiveSlot - rule := &SystemRule{MetricType: Concurrency, + var sas *AdaptiveSlot + rule := &Rule{MetricType: Concurrency, TriggerCount: 0.5} t.Run("TrueConcurrency", func(t *testing.T) { @@ -59,8 +59,8 @@ func TestDoCheckRuleConcurrency(t *testing.T) { } func TestDoCheckRuleLoad(t *testing.T) { - var sas *SystemAdaptiveSlot - rule := &SystemRule{MetricType: Load, + var sas *AdaptiveSlot + rule := &Rule{MetricType: Load, TriggerCount: 0.5} t.Run("TrueLoad", func(t *testing.T) { @@ -80,8 +80,8 @@ func TestDoCheckRuleLoad(t *testing.T) { } func TestDoCheckRuleCpuUsage(t *testing.T) { - var sas *SystemAdaptiveSlot - rule := &SystemRule{MetricType: CpuUsage, + var sas *AdaptiveSlot + rule := &Rule{MetricType: CpuUsage, TriggerCount: 0.5} t.Run("TrueCpuUsage", func(t *testing.T) { @@ -101,17 +101,11 @@ func TestDoCheckRuleCpuUsage(t *testing.T) { } func TestDoCheckRuleDefault(t *testing.T) { - var sas *SystemAdaptiveSlot - rule := &SystemRule{MetricType: MetricTypeSize, + var sas *AdaptiveSlot + rule := &Rule{MetricType: MetricTypeSize, TriggerCount: 0.5} isOK, v := sas.doCheckRule(rule) assert.Equal(t, true, isOK) assert.Equal(t, float64(0), v) } - -func TestString(t *testing.T) { - var sas *SystemAdaptiveSlot - - assert.True(t, sas.String() == SlotName) -} diff --git a/core/system/sys_stat.go b/core/system/sys_stat.go index ffb995789..358bb9b7d 100644 --- a/core/system/sys_stat.go +++ b/core/system/sys_stat.go @@ -57,11 +57,11 @@ func InitCollector(intervalMs uint32) { func retrieveAndUpdateSystemStat() { cpuStats, err := cpu.Times(false) if err != nil { - logging.Warnf("Failed to retrieve current CPU usage: %+v", err) + logging.Warn("Failed to retrieve current CPU usage", "err", err) } loadStat, err := load.Avg() if err != nil { - logging.Warnf("Failed to retrieve current system load: %+v", err) + logging.Warn("Failed to retrieve current system load", "err", err) } if len(cpuStats) > 0 { curCpuStat := &cpuStats[0] diff --git a/example/datasource/nacos/nacos_example.go b/example/datasource/nacos/nacos_example.go new file mode 100644 index 000000000..27821b927 --- /dev/null +++ b/example/datasource/nacos/nacos_example.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "math/rand" + "sync/atomic" + "time" + + sentinel "github.com/alibaba/sentinel-golang/api" + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/ext/datasource" + "github.com/alibaba/sentinel-golang/ext/datasource/nacos" + "github.com/alibaba/sentinel-golang/logging" + "github.com/alibaba/sentinel-golang/util" + "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/common/constant" +) + +type Counter struct { + pass *int64 + block *int64 + total *int64 +} + +func main() { + counter := Counter{pass: new(int64), block: new(int64), total: new(int64)} + + if err := sentinel.InitDefault(); err != nil { + fmt.Println(err) + return + } + + // For testing + if err := logging.ResetGlobalLogger(logging.NewConsoleLogger()); err != nil { + fmt.Println(err) + return + } + + //nacos server info + sc := []constant.ServerConfig{ + { + ContextPath: "/nacos", + Port: 8848, + IpAddr: "127.0.0.1", + }, + } + //nacos client info + cc := constant.ClientConfig{ + TimeoutMs: 5000, + } + //build nacos config client + client, err := clients.CreateConfigClient(map[string]interface{}{ + "serverConfigs": sc, + "clientConfig": cc, + }) + if err != nil { + fmt.Printf("Fail to create client, err: %+v", err) + return + } + h := datasource.NewFlowRulesHandler(datasource.FlowRuleJsonArrayParser) + //sentinel-go is nacos configuration management Group in flow control + //flow is nacos configuration management DataId in flow control + nds, err := nacos.NewNacosDataSource(client, "sentinel-go", "flow", h) + if err != nil { + fmt.Printf("Fail to create nacos data source client, err: %+v", err) + return + } + //initialize *NacosDataSource and load rule + err = nds.Initialize() + if err != nil { + fmt.Printf("Fail to initialize nacos data source client, err: %+v", err) + return + } + //Starting counter + go timerTask(&counter) + + //Simulation of the request + ch := make(chan struct{}) + for i := 0; i < 10; i++ { + go func() { + for { + atomic.AddInt64(counter.total, 1) + e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound)) + if b != nil { + atomic.AddInt64(counter.block, 1) + // Blocked. We could get the block reason from the BlockError. + time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) + } else { + atomic.AddInt64(counter.pass, 1) + time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) + + // Be sure the entry is exited finally. + e.Exit() + } + + } + }() + } + <-ch +} + +//statistic print +func timerTask(counter *Counter) { + fmt.Println("begin to statistic!!!") + var ( + oldTotal, oldPass, oldBlock int64 + ) + for { + time.Sleep(1 * time.Second) + globalTotal := atomic.LoadInt64(counter.total) + oneSecondTotal := globalTotal - oldTotal + oldTotal = globalTotal + + globalPass := atomic.LoadInt64(counter.pass) + oneSecondPass := globalPass - oldPass + oldPass = globalPass + + globalBlock := atomic.LoadInt64(counter.block) + oneSecondBlock := globalBlock - oldBlock + oldBlock = globalBlock + fmt.Println(util.CurrentTimeMillis()/1000, "total:", oneSecondTotal, " pass:", oneSecondPass, " block:", oneSecondBlock) + } +} diff --git a/example/isolation/concurrency_limitation_example.go b/example/isolation/concurrency_limitation_example.go new file mode 100644 index 000000000..e738ce161 --- /dev/null +++ b/example/isolation/concurrency_limitation_example.go @@ -0,0 +1,54 @@ +package main + +import ( + "math/rand" + "os" + "time" + + sentinel "github.com/alibaba/sentinel-golang/api" + "github.com/alibaba/sentinel-golang/core/config" + "github.com/alibaba/sentinel-golang/core/isolation" + "github.com/alibaba/sentinel-golang/logging" +) + +func main() { + cfg := config.NewDefaultConfig() + cfg.Sentinel.Log.Logger = logging.NewConsoleLogger() + cfg.Sentinel.Log.Metric.FlushIntervalSec = 0 + cfg.Sentinel.Stat.System.CollectIntervalMs = 0 + err := sentinel.InitWithConfig(cfg) + if err != nil { + logging.Error(err, "fail") + os.Exit(1) + } + logging.SetGlobalLoggerLevel(logging.DebugLevel) + ch := make(chan struct{}) + + r1 := &isolation.Rule{ + Resource: "abc", + MetricType: isolation.Concurrency, + Threshold: 12, + } + _, err = isolation.LoadRules([]*isolation.Rule{r1}) + if err != nil { + logging.Error(err, "fail") + os.Exit(1) + } + + for i := 0; i < 15; i++ { + go func() { + for { + e, b := sentinel.Entry("abc", sentinel.WithBatchCount(1)) + if b != nil { + logging.Info("blocked", "reason", b.BlockType().String(), "rule", b.TriggeredRule(), "snapshot", b.TriggeredValue()) + time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) + } else { + logging.Info("passed") + time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) + e.Exit() + } + } + }() + } + <-ch +} diff --git a/example/qps/qps_limit_example.go b/example/qps/qps_limit_example.go index 4760d6133..2e93c5ff4 100644 --- a/example/qps/qps_limit_example.go +++ b/example/qps/qps_limit_example.go @@ -19,12 +19,12 @@ func main() { log.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "some-test", - MetricType: flow.QPS, - Count: 10, - ControlBehavior: flow.Reject, + Resource: "some-test", + Threshold: 10, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) if err != nil { diff --git a/example/qps/qps_limit_example_test.go b/example/qps/qps_limit_example_test.go index b8ae70990..3c0aba760 100644 --- a/example/qps/qps_limit_example_test.go +++ b/example/qps/qps_limit_example_test.go @@ -26,12 +26,12 @@ func doTest() { log.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "some-test", - MetricType: flow.QPS, - Count: 100, - ControlBehavior: flow.Reject, + Resource: "some-test", + Threshold: 100, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, }, }) if err != nil { diff --git a/example/warm_up/qps_warm_up_example.go b/example/warm_up/qps_warm_up_example.go index 824e741d1..a97c3d300 100644 --- a/example/warm_up/qps_warm_up_example.go +++ b/example/warm_up/qps_warm_up_example.go @@ -26,19 +26,19 @@ func main() { counter := Counter{pass: new(int64), block: new(int64), total: new(int64)} // We should initialize Sentinel first. err := sentinel.InitDefault() - logging.ResetGlobalLogger(logging.NewConsoleLogger("flow-warmup-test")) + logging.ResetGlobalLogger(logging.NewConsoleLogger()) if err != nil { log.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "some-test", - MetricType: flow.QPS, - Count: 100, - ControlBehavior: flow.WarmUp, - WarmUpPeriodSec: 10, - WarmUpColdFactor: 3, + Resource: "some-test", + Threshold: 100, + TokenCalculateStrategy: flow.WarmUp, + ControlBehavior: flow.Reject, + WarmUpPeriodSec: 10, + WarmUpColdFactor: 3, }, }) if err != nil { diff --git a/example/warm_up/qps_warm_up_example_test.go b/example/warm_up/qps_warm_up_example_test.go index 1996c8c6e..519091772 100644 --- a/example/warm_up/qps_warm_up_example_test.go +++ b/example/warm_up/qps_warm_up_example_test.go @@ -28,13 +28,13 @@ func doTest() { log.Fatalf("Unexpected error: %+v", err) } - _, err = flow.LoadRules([]*flow.FlowRule{ + _, err = flow.LoadRules([]*flow.Rule{ { - Resource: "some-test", - MetricType: flow.QPS, - Count: 100, - ControlBehavior: flow.WarmUp, - WarmUpPeriodSec: 10, + Resource: "some-test", + Threshold: 100, + TokenCalculateStrategy: flow.WarmUp, + ControlBehavior: flow.Reject, + WarmUpPeriodSec: 10, }, }) if err != nil { diff --git a/ext/datasource/consul/consul.go b/ext/datasource/consul/consul.go index c978e19c8..1586c9fcc 100644 --- a/ext/datasource/consul/consul.go +++ b/ext/datasource/consul/consul.go @@ -83,7 +83,7 @@ func (c *consulDataSource) Initialize() error { } if err := c.doReadAndUpdate(); err != nil { // Failed to read default should't block initialization - logging.Errorf("[Consul] Failed to read initial data for key: %s, err: %s", c.propertyKey, err.Error()) + logging.Error(err, "[Consul] Failed to read initial data for key", "propertyKey", c.propertyKey) } go util.RunWithRecover(c.watch) @@ -92,7 +92,7 @@ func (c *consulDataSource) Initialize() error { } func (c *consulDataSource) watch() { - logging.Infof("[Consul] Consul data source is watching property: %s", c.propertyKey) + logging.Info("[Consul] Consul data source is watching property", "propertyKey", c.propertyKey) for { if err := c.doReadAndUpdate(); err != nil { if errors.Is(err, context.Canceled) { @@ -103,12 +103,12 @@ func (c *consulDataSource) watch() { } if api.IsRetryableError(err) { - logging.Warnf("[Consul] Update failed with retryable error: %s", err.Error()) + logging.Warn("[Consul] Update failed with retryable error", "err", err) time.Sleep(time.Second) continue } - logging.Errorf("[Consul] Failed to update data, key: %s, err: %s", c.propertyKey, err.Error()) + logging.Error(err, "[Consul] Failed to update data", "propertyKey", c.propertyKey) } } } @@ -132,6 +132,6 @@ func (c *consulDataSource) Close() error { if c.cancel != nil { c.cancel() } - logging.Infof("[Consul] Consul data source has been closed, key: %s", c.propertyKey) + logging.Info("[Consul] Consul data source has been closed", "propertyKey", c.propertyKey) return nil } diff --git a/ext/datasource/consul/consul_test.go b/ext/datasource/consul/consul_test.go index 8d085c24f..acf14dd9b 100644 --- a/ext/datasource/consul/consul_test.go +++ b/ext/datasource/consul/consul_test.go @@ -15,17 +15,17 @@ import ( const ( TestSystemRules = `[ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 2, + "id": "2", "metricType": 0, "adaptiveStrategy": 0 } @@ -33,7 +33,7 @@ const ( ) var ( - SystemRules = []*system.SystemRule{ + SystemRules = []*system.Rule{ {MetricType: 0, Strategy: 0}, {MetricType: 0, Strategy: 0}, {MetricType: 0, Strategy: 0}, @@ -61,7 +61,7 @@ func (c *consulClientMock) Get(key string, q *api.QueryOptions) (*api.KVPair, *a }, nil } -func (c consulClientMock) List(prefix string, q *api.QueryOptions) (api.KVPairs, *api.QueryMeta, error) { +func (c *consulClientMock) List(prefix string, q *api.QueryOptions) (api.KVPairs, *api.QueryMeta, error) { panic("implement me") } diff --git a/ext/datasource/datasource.go b/ext/datasource/datasource.go index d53df0e39..1de73b117 100644 --- a/ext/datasource/datasource.go +++ b/ext/datasource/datasource.go @@ -40,7 +40,8 @@ func (b *Base) Handle(src []byte) (err error) { if err == nil { return nil } - return Error{code: HandleSourceError, desc: fmt.Sprintf("%+v", err)} + + return NewError(HandleSourceError, fmt.Sprintf("%+v", err)) } // return idx if existed, else return -1 diff --git a/ext/datasource/datasource_test.go b/ext/datasource/datasource_test.go index e83c34b7f..6d9bc63e4 100644 --- a/ext/datasource/datasource_test.go +++ b/ext/datasource/datasource_test.go @@ -7,6 +7,17 @@ import ( "github.com/stretchr/testify/assert" ) +const ( + systemRule = `[ + { + "metricType": 0, + "triggerCount": 0.5, + "strategy": 1 + } +]` + systemRuleErr = "[test]" +) + func TestBase_AddPropertyHandler(t *testing.T) { t.Run("AddPropertyHandler_nil", func(t *testing.T) { b := &Base{ @@ -63,3 +74,24 @@ func TestBase_indexOfHandler(t *testing.T) { assert.True(t, b.indexOfHandler(h2) == 1, "Fail to execute the case TestBase_indexOfHandler.") }) } + +func TestBase_Handle(t *testing.T) { + t.Run("TestBase_handle", func(t *testing.T) { + b := &Base{ + handlers: make([]PropertyHandler, 0), + } + h := NewSystemRulesHandler(SystemRuleJsonArrayParser) + b.handlers = append(b.handlers, h) + err := b.Handle([]byte(systemRule)) + assert.Nil(t, err) + }) + t.Run("TestBase_handleErr", func(t *testing.T) { + b := &Base{ + handlers: make([]PropertyHandler, 0), + } + h := NewSystemRulesHandler(SystemRuleJsonArrayParser) + b.handlers = append(b.handlers, h) + err := b.Handle([]byte(systemRuleErr)) + assert.NotNil(t, err) + }) +} diff --git a/ext/datasource/etcdv3/etcdv3.go b/ext/datasource/etcdv3/etcdv3.go index 841a767c5..a0979ae22 100644 --- a/ext/datasource/etcdv3/etcdv3.go +++ b/ext/datasource/etcdv3/etcdv3.go @@ -42,7 +42,7 @@ func NewDatasource(client *clientv3.Client, key string, handlers ...datasource.P func (s *Etcdv3DataSource) Initialize() error { err := s.doReadAndUpdate() if err != nil { - logging.Errorf("Fail to update data for key[%s] when execute Initialize function, err: %+v", s.propertyKey, err) + logging.Error(err, "Fail to update data for key when execute Initialize function", "propertyKey", s.propertyKey) } go util.RunWithRecover(s.watch) return nil @@ -59,7 +59,8 @@ func (s *Etcdv3DataSource) ReadSource() ([]byte, error) { return nil, errors.Errorf("The key[%s] is not existed in etcd server.", s.propertyKey) } s.lastUpdatedRevision = resp.Header.GetRevision() - logging.Infof("Get the newest data for key:%s, revision: %d, value: %s", s.propertyKey, resp.Header.GetRevision(), resp.Kvs[0].Value) + logging.Info("Get the newest data for key", "propertyKey", s.propertyKey, + "revision", resp.Header.GetRevision(), "value", resp.Kvs[0].Value) return resp.Kvs[0].Value, nil } @@ -80,7 +81,7 @@ func (s *Etcdv3DataSource) processWatchResponse(resp *clientv3.WatchResponse) { } if err := resp.Err(); err != nil { - logging.Errorf("Watch on etcd endpoints(%+v) occur error, err: %+v", s.client.Endpoints(), err) + logging.Error(err, "Watch on etcd endpoints occur error", "endpointd", s.client.Endpoints()) return } @@ -88,13 +89,13 @@ func (s *Etcdv3DataSource) processWatchResponse(resp *clientv3.WatchResponse) { if ev.Type == mvccpb.PUT { err := s.doReadAndUpdate() if err != nil { - logging.Errorf("Fail to execute doReadAndUpdate for PUT event, err: %+v", err) + logging.Error(err, "Fail to execute doReadAndUpdate for PUT event") } } if ev.Type == mvccpb.DELETE { updateErr := s.Handle(nil) if updateErr != nil { - logging.Errorf("Fail to execute doReadAndUpdate for DELETE event, err: %+v", updateErr) + logging.Error(updateErr, "Fail to execute doReadAndUpdate for DELETE event") } } } diff --git a/ext/datasource/file/refreshable_file.go b/ext/datasource/file/refreshable_file.go index 230279be3..d716e2eb1 100644 --- a/ext/datasource/file/refreshable_file.go +++ b/ext/datasource/file/refreshable_file.go @@ -3,6 +3,7 @@ package file import ( "io/ioutil" "os" + "time" "github.com/alibaba/sentinel-golang/ext/datasource" "github.com/alibaba/sentinel-golang/logging" @@ -17,6 +18,7 @@ type RefreshableFileDataSource struct { isInitialized util.AtomicBool closeChan chan struct{} watcher *fsnotify.Watcher + closed util.AtomicBool } func NewFileDataSource(sourceFilePath string, handlers ...datasource.PropertyHandler) *RefreshableFileDataSource { @@ -51,7 +53,7 @@ func (s *RefreshableFileDataSource) Initialize() error { err := s.doReadAndUpdate() if err != nil { - logging.Errorf("Fail to execute doReadAndUpdate, err: %+v", err) + logging.Error(err, "Fail to execute doReadAndUpdate") } w, err := fsnotify.NewWatcher() @@ -69,22 +71,47 @@ func (s *RefreshableFileDataSource) Initialize() error { for { select { case ev := <-s.watcher.Events: - if ev.Op&fsnotify.Write == fsnotify.Write { - err := s.doReadAndUpdate() - if err != nil { - logging.Errorf("Fail to execute doReadAndUpdate, err: %+v", err) + if ev.Op&fsnotify.Rename == fsnotify.Rename { + logging.Warn("The file source was renamed.", "sourceFilePath", s.sourceFilePath) + updateErr := s.Handle(nil) + if updateErr != nil { + logging.Error(updateErr, "Fail to update nil property") } - } - if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename { - logging.Warnf("The file source [%s] was removed or renamed.", s.sourceFilePath) + // try to watch sourceFile + _ = s.watcher.Remove(s.sourceFilePath) + retryCount := 0 + for { + if retryCount > 5 { + logging.Error(errors.New("retry failed"), "Fail to retry watch", "sourceFilePath", s.sourceFilePath) + s.Close() + return + } + e := s.watcher.Add(s.sourceFilePath) + if e == nil { + break + } + retryCount++ + logging.Error(e, "Failed to add to watcher", "sourceFilePath", s.sourceFilePath) + time.Sleep(time.Second) + } + } + if ev.Op&fsnotify.Remove == fsnotify.Remove { + logging.Warn("The file source was removed.", "sourceFilePath", s.sourceFilePath) updateErr := s.Handle(nil) if updateErr != nil { - logging.Errorf("Fail to update nil property, err: %+v", updateErr) + logging.Error(updateErr, "Fail to update nil property") } + s.Close() + return + } + + err := s.doReadAndUpdate() + if err != nil { + logging.Error(err, "Fail to execute doReadAndUpdate") } case err := <-s.watcher.Errors: - logging.Errorf("Watch err on file[%s], err: %+v", s.sourceFilePath, err) + logging.Error(err, "Watch err on file", "sourceFilePath", s.sourceFilePath) case <-s.closeChan: return } @@ -103,7 +130,10 @@ func (s *RefreshableFileDataSource) doReadAndUpdate() (err error) { } func (s *RefreshableFileDataSource) Close() error { + if !s.closed.CompareAndSet(false, true) { + return nil + } s.closeChan <- struct{}{} - logging.Infof("The RefreshableFileDataSource for [%s] had been closed.", s.sourceFilePath) + logging.Info("The RefreshableFileDataSource for file had been closed.", "sourceFilePath", s.sourceFilePath) return nil } diff --git a/ext/datasource/file/refreshable_file_test.go b/ext/datasource/file/refreshable_file_test.go index c1599941f..f3977ebdb 100644 --- a/ext/datasource/file/refreshable_file_test.go +++ b/ext/datasource/file/refreshable_file_test.go @@ -17,17 +17,17 @@ import ( const ( TestSystemRules = `[ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 2, + "id": "2", "metricType": 0, "adaptiveStrategy": 0 } @@ -277,7 +277,7 @@ func TestNewFileDataSource_ALL_For_SystemRule(t *testing.T) { ds.Close() time.Sleep(1 * time.Second) e := ds.watcher.Add(TestSystemRulesFile) - assert.True(t, e != nil && strings.Contains(e.Error(), "closed")) + assert.True(t, e != nil) }) } diff --git a/ext/datasource/helper.go b/ext/datasource/helper.go index 397ebadc1..468cb1149 100644 --- a/ext/datasource/helper.go +++ b/ext/datasource/helper.go @@ -17,34 +17,34 @@ func checkSrcComplianceJson(src []byte) (bool, error) { return true, nil } -// FlowRuleJsonArrayParser provide JSON as the default serialization for list of flow.FlowRule +// FlowRuleJsonArrayParser provide JSON as the default serialization for list of flow.Rule func FlowRuleJsonArrayParser(src []byte) (interface{}, error) { if valid, err := checkSrcComplianceJson(src); !valid { return nil, err } - rules := make([]*flow.FlowRule, 0) + rules := make([]*flow.Rule, 0) err := json.Unmarshal(src, &rules) return rules, err } -// FlowRulesUpdater load the newest []flow.FlowRule to downstream flow component. +// FlowRulesUpdater load the newest []flow.Rule to downstream flow component. func FlowRulesUpdater(data interface{}) error { if data == nil { return flow.ClearRules() } - rules := make([]*flow.FlowRule, 0) - if val, ok := data.([]flow.FlowRule); ok { + rules := make([]*flow.Rule, 0) + if val, ok := data.([]flow.Rule); ok { for _, v := range val { rules = append(rules, &v) } - } else if val, ok := data.([]*flow.FlowRule); ok { + } else if val, ok := data.([]*flow.Rule); ok { rules = val } else { return Error{ code: UpdatePropertyError, - desc: fmt.Sprintf("Fail to type assert data to []flow.FlowRule or []*flow.FlowRule, in fact, data: %+v", data), + desc: fmt.Sprintf("Fail to type assert data to []flow.Rule or []*flow.Rule, in fact, data: %+v", data), } } succ, err := flow.LoadRules(rules) @@ -61,34 +61,34 @@ func NewFlowRulesHandler(converter PropertyConverter) PropertyHandler { return NewDefaultPropertyHandler(converter, FlowRulesUpdater) } -// SystemRuleJsonArrayParser provide JSON as the default serialization for list of system.SystemRule +// SystemRuleJsonArrayParser provide JSON as the default serialization for list of system.Rule func SystemRuleJsonArrayParser(src []byte) (interface{}, error) { if valid, err := checkSrcComplianceJson(src); !valid { return nil, err } - rules := make([]*system.SystemRule, 0) + rules := make([]*system.Rule, 0) err := json.Unmarshal(src, &rules) return rules, err } -// SystemRulesUpdater load the newest []system.SystemRule to downstream system component. +// SystemRulesUpdater load the newest []system.Rule to downstream system component. func SystemRulesUpdater(data interface{}) error { if data == nil { return system.ClearRules() } - rules := make([]*system.SystemRule, 0) - if val, ok := data.([]system.SystemRule); ok { + rules := make([]*system.Rule, 0) + if val, ok := data.([]system.Rule); ok { for _, v := range val { rules = append(rules, &v) } - } else if val, ok := data.([]*system.SystemRule); ok { + } else if val, ok := data.([]*system.Rule); ok { rules = val } else { return Error{ code: UpdatePropertyError, - desc: fmt.Sprintf("Fail to type assert data to []system.SystemRule or []*system.SystemRule, in fact, data: %+v", data), + desc: fmt.Sprintf("Fail to type assert data to []system.Rule or []*system.Rule, in fact, data: %+v", data), } } succ, err := system.LoadRules(rules) diff --git a/ext/datasource/helper_test.go b/ext/datasource/helper_test.go index f3411d6e2..f60259536 100644 --- a/ext/datasource/helper_test.go +++ b/ext/datasource/helper_test.go @@ -16,10 +16,14 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFlowRulesJsonConverter(t *testing.T) { +func TestFlowRuleJsonArrayParser(t *testing.T) { // Prepare test data f, err := os.Open("../../tests/testdata/extension/helper/FlowRule.json") - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + t.Fatal(err) + } + }() if err != nil { t.Errorf("The rules file is not existed, err:%+v.", errors.WithStack(err)) } @@ -28,7 +32,7 @@ func TestFlowRulesJsonConverter(t *testing.T) { t.Errorf("Fail to read file, err: %+v.", errors.WithStack(err)) } - t.Run("TestFlowRulesJsonConverter_nil", func(t *testing.T) { + t.Run("TestFlowRuleJsonArrayParser_Nil", func(t *testing.T) { got, err := FlowRuleJsonArrayParser(nil) assert.True(t, got == nil && err == nil) @@ -36,64 +40,49 @@ func TestFlowRulesJsonConverter(t *testing.T) { assert.True(t, got == nil && err == nil) }) - t.Run("TestFlowRulesJsonConverter_error", func(t *testing.T) { + t.Run("TestFlowRuleJsonArrayParser_Error", func(t *testing.T) { _, err := FlowRuleJsonArrayParser([]byte{'x', 'i', 'm', 'u'}) assert.True(t, err != nil) }) - t.Run("TestFlowRulesJsonConverter_normal", func(t *testing.T) { + t.Run("TestFlowRuleJsonArrayParser_Normal", func(t *testing.T) { got, err := FlowRuleJsonArrayParser(normalSrc) assert.True(t, got != nil && err == nil) - flowRules := got.([]*flow.FlowRule) + flowRules := got.([]*flow.Rule) assert.True(t, len(flowRules) == 3) - r1 := &flow.FlowRule{ - Resource: "abc", - LimitOrigin: "default", - MetricType: flow.Concurrency, - Count: 100, - RelationStrategy: flow.Direct, - ControlBehavior: flow.Reject, - RefResource: "refDefault", - WarmUpPeriodSec: 10, - MaxQueueingTimeMs: 1000, - ClusterMode: false, - ClusterConfig: flow.ClusterRuleConfig{ - ThresholdType: flow.AvgLocalThreshold, - }, + r1 := &flow.Rule{ + Resource: "abc", + Threshold: 100, + RelationStrategy: flow.CurrentResource, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + RefResource: "refDefault", + WarmUpPeriodSec: 10, + MaxQueueingTimeMs: 1000, } assert.True(t, reflect.DeepEqual(flowRules[0], r1)) - r2 := &flow.FlowRule{ - Resource: "abc", - LimitOrigin: "default", - MetricType: flow.QPS, - Count: 200, - RelationStrategy: flow.AssociatedResource, - ControlBehavior: flow.WarmUp, - RefResource: "refDefault", - WarmUpPeriodSec: 20, - MaxQueueingTimeMs: 2000, - ClusterMode: true, - ClusterConfig: flow.ClusterRuleConfig{ - ThresholdType: flow.GlobalThreshold, - }, + r2 := &flow.Rule{ + Resource: "abc", + Threshold: 200, + RelationStrategy: flow.AssociatedResource, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Throttling, + RefResource: "refDefault", + WarmUpPeriodSec: 20, + MaxQueueingTimeMs: 2000, } assert.True(t, reflect.DeepEqual(flowRules[1], r2)) - r3 := &flow.FlowRule{ - Resource: "abc", - LimitOrigin: "default", - MetricType: flow.QPS, - Count: 300, - RelationStrategy: flow.Direct, - ControlBehavior: flow.WarmUp, - RefResource: "refDefault", - WarmUpPeriodSec: 30, - MaxQueueingTimeMs: 3000, - ClusterMode: true, - ClusterConfig: flow.ClusterRuleConfig{ - ThresholdType: flow.GlobalThreshold, - }, + r3 := &flow.Rule{ + Resource: "abc", + Threshold: 300, + RelationStrategy: flow.CurrentResource, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Throttling, + RefResource: "refDefault", + WarmUpPeriodSec: 30, + MaxQueueingTimeMs: 3000, } assert.True(t, reflect.DeepEqual(flowRules[2], r3)) }) @@ -102,20 +91,16 @@ func TestFlowRulesJsonConverter(t *testing.T) { func TestFlowRulesUpdater(t *testing.T) { t.Run("TestFlowRulesUpdater_Nil", func(t *testing.T) { flow.ClearRules() - flow.LoadRules([]*flow.FlowRule{ + flow.LoadRules([]*flow.Rule{ { - ID: 0, - Resource: "abc", - LimitOrigin: "default", - MetricType: 0, - Count: 0, - RelationStrategy: 0, - ControlBehavior: 0, - RefResource: "", - WarmUpPeriodSec: 0, - MaxQueueingTimeMs: 0, - ClusterMode: false, - ClusterConfig: flow.ClusterRuleConfig{}, + Resource: "abc", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, }}) assert.True(t, len(flow.GetRules()) == 1, "Fail to prepare test data.") err := FlowRulesUpdater(nil) @@ -125,32 +110,28 @@ func TestFlowRulesUpdater(t *testing.T) { t.Run("TestFlowRulesUpdater_Assert_Failed", func(t *testing.T) { flow.ClearRules() err := FlowRulesUpdater("xxxxxxxx") - assert.True(t, err != nil && strings.Contains(err.Error(), "Fail to type assert data to []flow.FlowRule")) + assert.True(t, err != nil && strings.Contains(err.Error(), "Fail to type assert data to []flow.Rule")) }) t.Run("TestFlowRulesUpdater_Empty_Rules", func(t *testing.T) { flow.ClearRules() - p := make([]flow.FlowRule, 0) + p := make([]flow.Rule, 0) err := FlowRulesUpdater(p) assert.True(t, err == nil && len(flow.GetRules()) == 0) }) t.Run("TestFlowRulesUpdater_Normal", func(t *testing.T) { flow.ClearRules() - p := make([]flow.FlowRule, 0) - fw := flow.FlowRule{ - ID: 0, - Resource: "aaaa", - LimitOrigin: "aaa", - MetricType: 0, - Count: 0, - RelationStrategy: 0, - ControlBehavior: 0, - RefResource: "", - WarmUpPeriodSec: 0, - MaxQueueingTimeMs: 0, - ClusterMode: false, - ClusterConfig: flow.ClusterRuleConfig{}, + p := make([]flow.Rule, 0) + fw := flow.Rule{ + Resource: "aaaa", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, } p = append(p, fw) err := FlowRulesUpdater(p) @@ -158,55 +139,68 @@ func TestFlowRulesUpdater(t *testing.T) { }) } -func TestSystemRulesJsonConvert(t *testing.T) { - // Prepare test data - f, err := os.Open("../../tests/testdata/extension/helper/SystemRule.json") - defer f.Close() - if err != nil { - t.Errorf("The rules file is not existed, err:%+v.", errors.WithStack(err)) - } - normalSrc, err := ioutil.ReadAll(f) - if err != nil { - t.Errorf("Fail to read file, err: %+v.", errors.WithStack(err)) - } +func TestSystemRuleJsonArrayParser(t *testing.T) { + t.Run("TestSystemRuleJsonArrayParser_Normal", func(t *testing.T) { + // Prepare test data + f, err := os.Open("../../tests/testdata/extension/helper/SystemRule.json") + defer func() { + if err := f.Close(); err != nil { + t.Fatal(err) + } + }() + if err != nil { + t.Errorf("The rules file is not existed, err:%+v.", errors.WithStack(err)) + } + normalSrc, err := ioutil.ReadAll(f) + if err != nil { + t.Errorf("Fail to read file, err: %+v.", errors.WithStack(err)) + } - got, err := SystemRuleJsonArrayParser(normalSrc) - systemRules := got.([]*system.SystemRule) - assert.True(t, err == nil && len(systemRules) == 4) + got, err := SystemRuleJsonArrayParser(normalSrc) + systemRules := got.([]*system.Rule) + assert.True(t, err == nil && len(systemRules) == 4) - r0 := &system.SystemRule{ - MetricType: system.Load, - TriggerCount: 0.5, - Strategy: system.BBR, - } - r1 := &system.SystemRule{ - MetricType: system.AvgRT, - TriggerCount: 0.6, - Strategy: system.BBR, - } - r2 := &system.SystemRule{ - MetricType: system.Concurrency, - TriggerCount: 0.7, - Strategy: system.BBR, - } - r3 := &system.SystemRule{ - MetricType: system.InboundQPS, - TriggerCount: 0.8, - Strategy: system.BBR, - } + r0 := &system.Rule{ + MetricType: system.Load, + TriggerCount: 0.5, + Strategy: system.BBR, + } + r1 := &system.Rule{ + MetricType: system.AvgRT, + TriggerCount: 0.6, + Strategy: system.BBR, + } + r2 := &system.Rule{ + MetricType: system.Concurrency, + TriggerCount: 0.7, + Strategy: system.BBR, + } + r3 := &system.Rule{ + MetricType: system.InboundQPS, + TriggerCount: 0.8, + Strategy: system.BBR, + } - assert.True(t, reflect.DeepEqual(r0, systemRules[0])) - assert.True(t, reflect.DeepEqual(r1, systemRules[1])) - assert.True(t, reflect.DeepEqual(r2, systemRules[2])) - assert.True(t, reflect.DeepEqual(r3, systemRules[3])) + assert.True(t, reflect.DeepEqual(r0, systemRules[0])) + assert.True(t, reflect.DeepEqual(r1, systemRules[1])) + assert.True(t, reflect.DeepEqual(r2, systemRules[2])) + assert.True(t, reflect.DeepEqual(r3, systemRules[3])) + }) + + t.Run("TestSystemRuleJsonArrayParser_Nil", func(t *testing.T) { + got, err := SystemRuleJsonArrayParser(nil) + assert.True(t, got == nil && err == nil) + + got, err = SystemRuleJsonArrayParser([]byte{}) + assert.True(t, got == nil && err == nil) + }) } func TestSystemRulesUpdater(t *testing.T) { t.Run("TestSystemRulesUpdater_Nil", func(t *testing.T) { system.ClearRules() - system.LoadRules([]*system.SystemRule{ + system.LoadRules([]*system.Rule{ { - ID: 0, MetricType: 0, TriggerCount: 0, Strategy: 0, @@ -220,21 +214,20 @@ func TestSystemRulesUpdater(t *testing.T) { t.Run("TestSystemRulesUpdater_Assert_Failed", func(t *testing.T) { system.ClearRules() err := SystemRulesUpdater("xxxxxxxx") - assert.True(t, err != nil && strings.Contains(err.Error(), "Fail to type assert data to []system.SystemRule")) + assert.True(t, err != nil && strings.Contains(err.Error(), "Fail to type assert data to []system.Rule")) }) t.Run("TestSystemRulesUpdater_Empty_Rules", func(t *testing.T) { system.ClearRules() - p := make([]system.SystemRule, 0) + p := make([]system.Rule, 0) err := SystemRulesUpdater(p) assert.True(t, err == nil && len(system.GetRules()) == 0) }) t.Run("TestSystemRulesUpdater_Normal", func(t *testing.T) { system.ClearRules() - p := make([]system.SystemRule, 0) - sr := system.SystemRule{ - ID: 0, + p := make([]system.Rule, 0) + sr := system.Rule{ MetricType: 0, TriggerCount: 0, Strategy: 0, @@ -245,16 +238,20 @@ func TestSystemRulesUpdater(t *testing.T) { }) } -func TestCircuitBreakerRulesJsonConverter(t *testing.T) { - t.Run("TestCircuitBreakerRulesJsonConverter_failed", func(t *testing.T) { +func TestCircuitBreakerRuleJsonArrayParser(t *testing.T) { + t.Run("TestCircuitBreakerRuleJsonArrayParser_Failed", func(t *testing.T) { _, err := CircuitBreakerRuleJsonArrayParser([]byte{'s', 'r', 'c'}) assert.True(t, err != nil) }) - t.Run("TestCircuitBreakerRulesJsonConverter_Succeed", func(t *testing.T) { + t.Run("TestCircuitBreakerRuleJsonArrayParser_Succeed", func(t *testing.T) { // Prepare test data f, err := os.Open("../../tests/testdata/extension/helper/CircuitBreakerRule.json") - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + t.Fatal(err) + } + }() if err != nil { t.Errorf("The rules file is not existed, err:%+v.", err) } @@ -293,56 +290,91 @@ func TestCircuitBreakerRulesJsonConverter(t *testing.T) { Threshold: 30, })) }) + + t.Run("TestCircuitBreakerRuleJsonArrayParser_Nil", func(t *testing.T) { + got, err := CircuitBreakerRuleJsonArrayParser(nil) + assert.True(t, got == nil && err == nil) + + got, err = CircuitBreakerRuleJsonArrayParser([]byte{}) + assert.True(t, got == nil && err == nil) + }) } func TestCircuitBreakerRulesUpdater(t *testing.T) { - // Prepare test data - r1 := &cb.Rule{ - Resource: "abc", - Strategy: cb.SlowRequestRatio, - RetryTimeoutMs: 1000, - MinRequestAmount: 5, - StatIntervalMs: 1000, - MaxAllowedRtMs: 20, - Threshold: 0.1, - } - r2 := &cb.Rule{ - Resource: "abc", - Strategy: cb.ErrorRatio, - RetryTimeoutMs: 1000, - MinRequestAmount: 5, - StatIntervalMs: 1000, - Threshold: 0.3, - } - r3 := &cb.Rule{ - Resource: "abc", - Strategy: cb.ErrorCount, - RetryTimeoutMs: 1000, - MinRequestAmount: 5, - StatIntervalMs: 1000, - Threshold: 10, - } + t.Run("TestCircuitBreakerRulesUpdater_Normal", func(t *testing.T) { + // Prepare test data + r1 := &cb.Rule{ + Resource: "abc", + Strategy: cb.SlowRequestRatio, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + MaxAllowedRtMs: 20, + Threshold: 0.1, + } + r2 := &cb.Rule{ + Resource: "abc", + Strategy: cb.ErrorRatio, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + Threshold: 0.3, + } + r3 := &cb.Rule{ + Resource: "abc", + Strategy: cb.ErrorCount, + RetryTimeoutMs: 1000, + MinRequestAmount: 5, + StatIntervalMs: 1000, + Threshold: 10, + } - err := CircuitBreakerRulesUpdater([]*cb.Rule{r1, r2, r3}) - assert.True(t, err == nil) + err := CircuitBreakerRulesUpdater([]*cb.Rule{r1, r2, r3}) + assert.True(t, err == nil) - rules := cb.GetResRules("abc") - assert.True(t, reflect.DeepEqual(rules[0], r1)) - assert.True(t, reflect.DeepEqual(rules[1], r2)) - assert.True(t, reflect.DeepEqual(rules[2], r3)) + rules := cb.GetRulesOfResource("abc") + assert.True(t, reflect.DeepEqual(rules[0], *r1)) + assert.True(t, reflect.DeepEqual(rules[1], *r2)) + assert.True(t, reflect.DeepEqual(rules[2], *r3)) + }) + t.Run("TestCircuitBreakerRulesUpdater_Nil", func(t *testing.T) { + err := CircuitBreakerRulesUpdater(nil) + assert.Nil(t, err) + }) + t.Run("TestCircuitBreakerRulesUpdater_Type_Err", func(t *testing.T) { + rules := []*flow.Rule{ + { + Resource: "abc", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, + }} + err := CircuitBreakerRulesUpdater(rules) + + assert.True(t, err.(Error).Code() == UpdatePropertyError) + assert.True(t, strings.Contains(err.(Error).desc, "Fail to type assert")) + }) } -func TestHotSpotParamRuleListJsonConverter(t *testing.T) { +func TestHotSpotParamRuleJsonArrayParser(t *testing.T) { t.Run("TestHotSpotParamRuleListJsonConverter_invalid", func(t *testing.T) { properties, err := HotSpotParamRuleJsonArrayParser([]byte{'s', 'r', 'c'}) assert.True(t, properties == nil) assert.True(t, err != nil) }) - t.Run("TestHotSpotParamRuleListJsonConverter_normal", func(t *testing.T) { + t.Run("TestHotSpotParamRuleListJsonConverter_Normal", func(t *testing.T) { // Prepare test data f, err := os.Open("../../tests/testdata/extension/helper/HotSpotParamFlowRule.json") - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + t.Fatal(err) + } + }() if err != nil { t.Errorf("The rules file is not existed, err:%+v.", err) } @@ -363,101 +395,129 @@ func TestHotSpotParamRuleListJsonConverter(t *testing.T) { assert.True(t, strings.Contains(rules[2].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Reject, ParamIndex:2, Threshold:3000.000000, MaxQueueingTimeMs:3, BurstCount:30, DurationInSec:3, ParamsMaxCapacity:30000, SpecificItems:[{ValKind:KindInt ValStr:1000 Threshold:30001} {ValKind:KindString ValStr:ximu Threshold:30002} {ValKind:KindBool ValStr:true Threshold:30003}]}")) assert.True(t, strings.Contains(rules[3].String(), "Resource:abc, MetricType:QPS, ControlBehavior:Throttling, ParamIndex:3, Threshold:4000.000000, MaxQueueingTimeMs:4, BurstCount:40, DurationInSec:4, ParamsMaxCapacity:40000, SpecificItems:[{ValKind:KindInt ValStr:1000 Threshold:40001} {ValKind:KindString ValStr:ximu Threshold:40002} {ValKind:KindBool ValStr:true Threshold:40003}]}")) }) + + t.Run("TestHotSpotParamRuleListJsonConverter_Nil", func(t *testing.T) { + got, err := HotSpotParamRuleJsonArrayParser(nil) + assert.True(t, got == nil && err == nil) + + got, err = HotSpotParamRuleJsonArrayParser([]byte{}) + assert.True(t, got == nil && err == nil) + }) } func TestHotSpotParamRuleListJsonUpdater(t *testing.T) { - // Prepare test data - m := make([]hotspot.SpecificValue, 2) - m[0] = hotspot.SpecificValue{ - ValKind: hotspot.KindString, - ValStr: "sss", - Threshold: 1, - } - m[1] = hotspot.SpecificValue{ - ValKind: hotspot.KindFloat64, - ValStr: "1.123", - Threshold: 3, - } - r1 := &hotspot.Rule{ - ID: "1", - Resource: "abc", - MetricType: hotspot.Concurrency, - ControlBehavior: hotspot.Reject, - ParamIndex: 0, - Threshold: 100, - MaxQueueingTimeMs: 0, - BurstCount: 10, - DurationInSec: 1, - SpecificItems: m, - } + t.Run("TestHotSpotParamRuleListJsonUpdater_Normal", func(t *testing.T) { + // Prepare test data + m := make([]hotspot.SpecificValue, 2) + m[0] = hotspot.SpecificValue{ + ValKind: hotspot.KindString, + ValStr: "sss", + Threshold: 1, + } + m[1] = hotspot.SpecificValue{ + ValKind: hotspot.KindFloat64, + ValStr: "1.123", + Threshold: 3, + } + r1 := &hotspot.Rule{ + ID: "1", + Resource: "abc", + MetricType: hotspot.Concurrency, + ControlBehavior: hotspot.Reject, + ParamIndex: 0, + Threshold: 100, + MaxQueueingTimeMs: 0, + BurstCount: 10, + DurationInSec: 1, + SpecificItems: m, + } - m2 := make([]hotspot.SpecificValue, 2) - m2[0] = hotspot.SpecificValue{ - ValKind: hotspot.KindString, - ValStr: "sss", - Threshold: 1, - } - m2[1] = hotspot.SpecificValue{ - ValKind: hotspot.KindFloat64, - ValStr: "1.123", - Threshold: 3, - } - r2 := &hotspot.Rule{ - ID: "2", - Resource: "abc", - MetricType: hotspot.QPS, - ControlBehavior: hotspot.Throttling, - ParamIndex: 1, - Threshold: 100, - MaxQueueingTimeMs: 20, - BurstCount: 0, - DurationInSec: 1, - SpecificItems: m2, - } + m2 := make([]hotspot.SpecificValue, 2) + m2[0] = hotspot.SpecificValue{ + ValKind: hotspot.KindString, + ValStr: "sss", + Threshold: 1, + } + m2[1] = hotspot.SpecificValue{ + ValKind: hotspot.KindFloat64, + ValStr: "1.123", + Threshold: 3, + } + r2 := &hotspot.Rule{ + ID: "2", + Resource: "abc", + MetricType: hotspot.QPS, + ControlBehavior: hotspot.Throttling, + ParamIndex: 1, + Threshold: 100, + MaxQueueingTimeMs: 20, + BurstCount: 0, + DurationInSec: 1, + SpecificItems: m2, + } - m3 := make([]hotspot.SpecificValue, 2) - m3[0] = hotspot.SpecificValue{ - ValKind: hotspot.KindString, - ValStr: "sss", - Threshold: 1, - } - m3[1] = hotspot.SpecificValue{ - ValKind: hotspot.KindFloat64, - ValStr: "1.123", - Threshold: 3, - } - r3 := &hotspot.Rule{ - ID: "3", - Resource: "abc", - MetricType: hotspot.Concurrency, - ControlBehavior: hotspot.Throttling, - ParamIndex: 2, - Threshold: 100, - MaxQueueingTimeMs: 20, - BurstCount: 0, - DurationInSec: 1, - SpecificItems: m3, - } + m3 := make([]hotspot.SpecificValue, 2) + m3[0] = hotspot.SpecificValue{ + ValKind: hotspot.KindString, + ValStr: "sss", + Threshold: 1, + } + m3[1] = hotspot.SpecificValue{ + ValKind: hotspot.KindFloat64, + ValStr: "1.123", + Threshold: 3, + } + r3 := &hotspot.Rule{ + ID: "3", + Resource: "abc", + MetricType: hotspot.Concurrency, + ControlBehavior: hotspot.Throttling, + ParamIndex: 2, + Threshold: 100, + MaxQueueingTimeMs: 20, + BurstCount: 0, + DurationInSec: 1, + SpecificItems: m3, + } - r4 := &hotspot.Rule{ - ID: "4", - Resource: "abc", - MetricType: hotspot.Concurrency, - ControlBehavior: hotspot.Throttling, - ParamIndex: 2, - Threshold: 100, - MaxQueueingTimeMs: 20, - BurstCount: 0, - DurationInSec: 2, - SpecificItems: m3, - } + r4 := &hotspot.Rule{ + ID: "4", + Resource: "abc", + MetricType: hotspot.Concurrency, + ControlBehavior: hotspot.Throttling, + ParamIndex: 2, + Threshold: 100, + MaxQueueingTimeMs: 20, + BurstCount: 0, + DurationInSec: 2, + SpecificItems: m3, + } + + err := HotSpotParamRulesUpdater([]*hotspot.Rule{r1, r2, r3, r4}) + assert.True(t, err == nil) - err := HotSpotParamRulesUpdater([]*hotspot.Rule{r1, r2, r3, r4}) - assert.True(t, err == nil) + rules := hotspot.GetRulesOfResource("abc") + assert.True(t, reflect.DeepEqual(rules[0], *r1)) + assert.True(t, reflect.DeepEqual(rules[1], *r2)) + assert.True(t, reflect.DeepEqual(rules[2], *r3)) + assert.True(t, reflect.DeepEqual(rules[3], *r4)) + }) - rules := hotspot.GetRules("abc") - assert.True(t, rules[0].Equals(r1)) - assert.True(t, rules[1].Equals(r2)) - assert.True(t, rules[2].Equals(r3)) - assert.True(t, rules[3].Equals(r4)) + t.Run("TestHotSpotParamRuleListJsonUpdater_Type_Err", func(t *testing.T) { + rules := []*flow.Rule{ + { + Resource: "abc", + Threshold: 0, + RelationStrategy: 0, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + RefResource: "", + WarmUpPeriodSec: 0, + MaxQueueingTimeMs: 0, + }} + err := HotSpotParamRulesUpdater(rules) + + assert.True(t, err.(Error).Code() == UpdatePropertyError) + assert.True(t, strings.Contains(err.(Error).desc, "Fail to type assert")) + }) } diff --git a/ext/datasource/nacos/nacos.go b/ext/datasource/nacos/nacos.go index 5f4a25f53..9d063549c 100644 --- a/ext/datasource/nacos/nacos.go +++ b/ext/datasource/nacos/nacos.go @@ -51,7 +51,7 @@ func (s *NacosDataSource) Initialize() error { } err = s.listen(s.client) if err == nil { - logging.Infof("Nacos data source is successfully initialized, group: %s, dataId: %s", s.group, s.dataId) + logging.Info("nacos data source is successfully initialized", "group", s.group, "dataId", s.dataId) } return err } @@ -65,7 +65,7 @@ func (s *NacosDataSource) ReadSource() ([]byte, error) { return nil, errors.Errorf("Failed to read the nacos data source when initialization, err: %+v", err) } - logging.Infof("Succeed to read source for group: %s, dataId: %s, data: %s", s.group, s.dataId, content) + logging.Info("succeed to read source", "group", s.group, "dataId", s.dataId, "content", content) return []byte(content), err } @@ -78,10 +78,10 @@ func (s *NacosDataSource) listen(client config_client.IConfigClient) (err error) DataId: s.dataId, Group: s.group, OnChange: func(namespace, group, dataId, data string) { - logging.Infof("Receive listened property. namespace: %s, group: %s, dataId: %s, data: %s", namespace, group, dataId, data) + logging.Info("receive listened property", "namespace", namespace, "group", group, "dataId", dataId, "data", data) err := s.doUpdate([]byte(data)) if err != nil { - logging.Errorf("Fail to update data source, err: %+v", err) + logging.Error(err, "fail to update data source") } }, } @@ -100,6 +100,6 @@ func (s *NacosDataSource) Close() error { if err != nil { return errors.Errorf("Failed to cancel listen to the nacos data source, err: %+v", err) } - logging.Infof("The nacos datasource had been closed, group: %s, dataId: %s", s.group, s.dataId) + logging.Info("the nacos datasource had been closed", "group", s.group, "dataId", s.dataId) return nil } diff --git a/ext/datasource/nacos/nacos_example.go b/ext/datasource/nacos/nacos_example.go deleted file mode 100644 index e9cf6b091..000000000 --- a/ext/datasource/nacos/nacos_example.go +++ /dev/null @@ -1,60 +0,0 @@ -package nacos - -import ( - "fmt" - "time" - - "github.com/alibaba/sentinel-golang/ext/datasource" - "github.com/nacos-group/nacos-sdk-go/clients/config_client" - "github.com/nacos-group/nacos-sdk-go/clients/nacos_client" - "github.com/nacos-group/nacos-sdk-go/common/constant" - "github.com/nacos-group/nacos-sdk-go/common/http_agent" - "github.com/stretchr/testify/mock" -) - -var serverConfig = constant.ServerConfig{ - ContextPath: "/nacos", - Port: 8848, - IpAddr: "127.0.0.1", -} - -var clientConfigTest = constant.ClientConfig{ - BeatInterval: 10000, - TimeoutMs: 10000, - ListenInterval: 20000, -} - -func createConfigClientTest() (*config_client.ConfigClient, error) { - nc := nacos_client.NacosClient{} - err := nc.SetServerConfig([]constant.ServerConfig{serverConfig}) - err = nc.SetClientConfig(clientConfigTest) - err = nc.SetHttpAgent(&http_agent.HttpAgent{}) - client, err := config_client.NewConfigClient(&nc) - - return &client, err -} - -func Example_NacosDatasource_CustomizeClient() { - client, err := createConfigClientTest() - if err != nil { - fmt.Printf("Fail to create client, err: %+v", err) - return - } - h := &datasource.MockPropertyHandler{} - h.On("isPropertyConsistent", mock.Anything).Return(true) - h.On("Handle", mock.Anything).Return(nil) - nds, err := NewNacosDataSource(client, "sentinel-go", "system-rules", h) - if err != nil { - fmt.Printf("Fail to create nacos data source client, err: %+v", err) - return - } - err = nds.Initialize() - if err != nil { - fmt.Printf("Fail to initialize nacos data source client, err: %+v", err) - return - } - - time.Sleep(time.Second * 10) - nds.Close() - fmt.Println("Nacos datasource is Closed") -} diff --git a/ext/datasource/nacos/nacos_test.go b/ext/datasource/nacos/nacos_test.go index 38eb23ac4..1b278c721 100644 --- a/ext/datasource/nacos/nacos_test.go +++ b/ext/datasource/nacos/nacos_test.go @@ -3,30 +3,30 @@ package nacos import ( "testing" - "github.com/nacos-group/nacos-sdk-go/clients/config_client" - "github.com/alibaba/sentinel-golang/ext/datasource" - "github.com/stretchr/testify/assert" - + "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/model" "github.com/nacos-group/nacos-sdk-go/vo" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) const ( TestSystemRules = `[ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 2, + "id": "2", "metricType": 0, "adaptiveStrategy": 0 } @@ -83,14 +83,28 @@ func getNacosDataSource(client config_client.IConfigClient) (*NacosDataSource, e func TestNacosDataSource(t *testing.T) { - t.Run("NewNacosDataSource", func(t *testing.T) { - client, err := createConfigClientTest() + t.Run("TestNewNacosDataSource", func(t *testing.T) { + sc := []constant.ServerConfig{ + { + ContextPath: "/nacos", + Port: 8848, + IpAddr: "127.0.0.1", + }, + } + + cc := constant.ClientConfig{ + TimeoutMs: 5000, + } + client, err := clients.CreateConfigClient(map[string]interface{}{ + "serverConfigs": sc, + "clientConfig": cc, + }) assert.Nil(t, err) nds, err := getNacosDataSource(client) assert.True(t, nds != nil && err == nil) }) - t.Run("NacosDataSource_Initialize", func(t *testing.T) { + t.Run("TestNacosDataSourceInitialize", func(t *testing.T) { mh1 := &datasource.MockPropertyHandler{} mh1.On("Handle", mock.Anything).Return(nil) mh1.On("isPropertyConsistent", mock.Anything).Return(false) @@ -102,4 +116,17 @@ func TestNacosDataSource(t *testing.T) { err = nds.Initialize() assert.True(t, err == nil) }) + + t.Run("TestNacosDataSourceClose", func(t *testing.T) { + mh1 := &datasource.MockPropertyHandler{} + mh1.On("Handle", mock.Anything).Return(nil) + mh1.On("isPropertyConsistent", mock.Anything).Return(false) + nacosClientMock := new(nacosClientMock) + nacosClientMock.On("CancelListenConfig", mock.Anything).Return(nil) + nds, err := getNacosDataSource(nacosClientMock) + assert.True(t, nds != nil && err == nil) + assert.True(t, nds != nil) + err = nds.Close() + assert.True(t, err == nil) + }) } diff --git a/ext/datasource/property.go b/ext/datasource/property.go index ed0790290..5c7ad9063 100644 --- a/ext/datasource/property.go +++ b/ext/datasource/property.go @@ -49,7 +49,7 @@ func (h *DefaultPropertyHandler) isPropertyConsistent(src interface{}) bool { func (h *DefaultPropertyHandler) Handle(src []byte) error { defer func() { if err := recover(); err != nil { - logging.Panicf("Unexpected panic: %+v", errors.Errorf("%+v", err)) + logging.Error(errors.Errorf("%+v", err), "Unexpected panic", "err") } }() // convert to target property diff --git a/ext/datasource/property_test.go b/ext/datasource/property_test.go index 2151f3971..b4d6e6e3e 100644 --- a/ext/datasource/property_test.go +++ b/ext/datasource/property_test.go @@ -11,7 +11,7 @@ import ( ) func MockSystemRulesConverter(src []byte) (interface{}, error) { - ret := make([]system.SystemRule, 0) + ret := make([]system.Rule, 0) _ = json.Unmarshal(src, &ret) return ret, nil } @@ -50,7 +50,7 @@ func TestSinglePropertyHandler_isPropertyConsistent(t *testing.T) { if err != nil { t.Errorf("Fail to get source file, err:%+v", err) } - ret1 := make([]system.SystemRule, 0) + ret1 := make([]system.Rule, 0) _ = json.Unmarshal(src, &ret1) isConsistent := h.isPropertyConsistent(ret1) assert.True(t, isConsistent == false, "Fail to execute isPropertyConsistent.") @@ -59,7 +59,7 @@ func TestSinglePropertyHandler_isPropertyConsistent(t *testing.T) { if err != nil { t.Errorf("Fail to get source file, err:%+v", err) } - ret2 := make([]system.SystemRule, 0) + ret2 := make([]system.Rule, 0) _ = json.Unmarshal(src2, &ret2) isConsistent = h.isPropertyConsistent(ret2) assert.True(t, isConsistent == true, "Fail to execute isPropertyConsistent.") @@ -68,7 +68,7 @@ func TestSinglePropertyHandler_isPropertyConsistent(t *testing.T) { if err != nil { t.Errorf("Fail to get source file, err:%+v", err) } - ret3 := make([]system.SystemRule, 0) + ret3 := make([]system.Rule, 0) _ = json.Unmarshal(src3, &ret3) isConsistent = h.isPropertyConsistent(ret3) assert.True(t, isConsistent == false, "Fail to execute isPropertyConsistent.") diff --git a/go.mod b/go.mod index 1c652e7fa..9cd215f4f 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.13 require ( github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/apache/dubbo-go v1.4.2 - github.com/coreos/etcd v3.3.18+incompatible + github.com/coreos/etcd v3.3.25+incompatible + github.com/dustin/go-humanize v1.0.0 // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/gin-gonic/gin v1.5.0 github.com/go-ole/go-ole v1.2.4 // indirect + github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.4.0 github.com/google/uuid v1.1.1 github.com/hashicorp/consul/api v1.4.0 @@ -23,3 +24,7 @@ require ( google.golang.org/grpc v1.26.0 gopkg.in/yaml.v2 v2.2.8 ) + +replace github.com/coreos/bbolt v1.3.5 => go.etcd.io/bbolt v1.3.5 + +replace github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0 diff --git a/go.sum b/go.sum index d3deaf9ac..e5ebe6ad0 100644 --- a/go.sum +++ b/go.sum @@ -12,11 +12,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw= github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= @@ -32,29 +29,16 @@ github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvd github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Jeffail/gabs v1.1.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.7-0.20191101173118-65519b62243c/go.mod h1:7xhjOwRV2+0HXGmM0jxaEu+ZiXJFoVZOTfL/dmqbrD8= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/SAP/go-hdb v0.12.0/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= -github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= -github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k= github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -62,19 +46,15 @@ github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apache/dubbo-go v1.4.2 h1:sp7dmpILhWKGwRVaSxBP9EVlvaQjp3njLsf0u603+24= -github.com/apache/dubbo-go v1.4.2/go.mod h1:KEMiKpHQDsRMywgiRzM6EdlqMu6dlZJBmfqKCK1LTMU= -github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -82,48 +62,40 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/caddyserver/certmagic v0.10.6/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ= -github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.18+incompatible h1:Zz1aXgDrFFi1nadh58tA9ktt06cmPTwNNP3dXwIq1lE= github.com/coreos/etcd v3.3.18+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.25+incompatible h1:0GQEw6h3YnuOVdtwygkIfJ+Omx0tZ8/QkVyXI4LkbeY= +github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok= @@ -131,47 +103,27 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= -github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20180620032804-94c9c97e8c9f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= -github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/digitalocean/godo v1.1.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20191101170500-ac7306503d23/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= -github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= -github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= -github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ef-ds/deque v1.0.4-0.20190904040645-54cb57c252a1/go.mod h1:HvODWzv6Y6kBf3Ah2WzN1bHjDUezGLaAhwuWVwfpEJs= -github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.0.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= @@ -180,20 +132,20 @@ github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/forestgiant/sliceutil v0.0.0-20160425183142-94783f95db6c/go.mod h1:pFdJbAhRf7rh6YYMUdIQGyzne6zYL1tCUW8QV2B3UfY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/go-dockerclient v1.6.0/go.mod h1:YWwtNPuL4XTX1SKJQk86cWPmmqwx+4np9qfPbb+znGc= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= @@ -203,58 +155,47 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc= github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= -github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= -github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.3/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gocql/gocql v0.0.0-20180617115710-e06f8c1bcd78/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -263,22 +204,16 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -288,11 +223,7 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -300,58 +231,35 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul v1.5.3 h1:EmTWRf/cuqZk6Ug9tgFUVE9xNgJPpmBvJwJMvm+agSk= -github.com/hashicorp/consul v1.5.3/go.mod h1:61E2GJCPEP3oq8La7sfDdWGQ66+Zbxzw5ecOdFD7xIE= -github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY= github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30= github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.0/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= -github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd/go.mod h1:ueUgD9BeIocT7QNuvxSyJyPAM9dfifBcaWmeybb67OY= -github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v0.0.0-20180331002553-e8d22c780116/go.mod h1:JSqWYsict+jzcj0+xElxyrBQRPNoiWQuddnxArJ7XHQ= -github.com/hashicorp/go-raftchunking v0.6.1/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= @@ -360,83 +268,61 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v0.0.0-20170202080759-03c5bf6be031/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v0.0.0-20180906183839-65a6292f0157/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= -github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= -github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= -github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault v0.10.3/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= -github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20190318174639-195e0e9d07f1/go.mod h1:VJHHT2SC1tAPrfENQeBhLlb5FbZoKZM+oC/ROmEftz0= -github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 h1:mGIXW/lubQ4B+3bXTLxcTMTjUNDqoF6T/HUW9LbFx9s= -github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= @@ -452,25 +338,21 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgU github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= -github.com/lib/pq v0.0.0-20180523175426-90697d60dd84/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= github.com/lucas-clemente/quic-go v0.14.1/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE= github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -485,46 +367,38 @@ github.com/micro/cli/v2 v2.1.2 h1:43J1lChg/rZCC1rvdqZNFSQDrGT7qfMrtp6/ztpIkEM= github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOtGtYg= github.com/micro/go-micro/v2 v2.9.1 h1:+S9koIrNWARjpP6k2TZ7kt0uC9zUJtNXzIdZTZRms7Q= github.com/micro/go-micro/v2 v2.9.1/go.mod h1:x55ZM3Puy0FyvvkR3e0ha0xsE9DFwfPSUMWAIbFY0SY= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= github.com/nacos-group/nacos-sdk-go v1.0.0 h1:CufUF7DZca2ZzIrJtMMCDih1sA58BWCglArLMCZArUc= github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.6 h1:qAaHZaS8pRRNQLFaiBA1rq5WynyEGp9DFgmMfoaiXGY= github.com/nats-io/nats-server/v2 v2.1.6/go.mod h1:BL1NOtaBQ5/y97djERRVWNouMW7GT3gxnmbE/eC8u8A= github.com/nats-io/nats.go v1.9.2 h1:oDeERm3NcZVrPpdR/JpGdWHMv3oJ8yY30YwxKq+DU2s= github.com/nats-io/nats.go v1.9.2/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE= @@ -534,21 +408,16 @@ github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1t github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nlopes/slack v0.6.1-0.20191106133607-d06c2a2b3249/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= -github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -560,101 +429,75 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= -github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= -github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v0.0.0-20180527043350-9f6ff22cfff8/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.19.12+incompatible h1:WRstheAymn1WOPesh+24+bZKFkqrdCR8JOc77v4xV3Q= github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= -github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -662,15 +505,12 @@ github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= -github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc h1:yUaosFVTJwnltaHbSNC3i82I92quFs+OFPRl8kNMVwo= github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -683,7 +523,6 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= @@ -694,22 +533,17 @@ github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4m github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= @@ -717,7 +551,6 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= @@ -729,7 +562,6 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -737,7 +569,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -756,8 +587,8 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -766,7 +597,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -785,9 +615,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -797,7 +625,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -808,7 +635,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -828,9 +654,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -842,29 +666,28 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -881,11 +704,11 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -893,7 +716,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -904,7 +726,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -912,6 +733,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= @@ -922,8 +744,6 @@ google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -931,34 +751,29 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0= gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= -gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -969,7 +784,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= @@ -982,16 +796,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= -k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.0.0-20190325185214-7544f9db76f6/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/apimachinery v0.0.0-20180821005732-488889b0007f/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/client-go v8.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/logging/logging.go b/logging/logging.go index 904dfad0e..dedcc3ad3 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -1,10 +1,16 @@ package logging import ( + "encoding/json" "errors" "fmt" "log" "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" ) // Level represents the level of logging. @@ -15,20 +21,23 @@ const ( InfoLevel WarnLevel ErrorLevel - FatalLevel - PanicLevel ) const ( - DefaultNamespace = "default" // RecordLogFileName represents the default file name of the record log. RecordLogFileName = "sentinel-record.log" - DefaultDirName = "logs" + string(os.PathSeparator) + "csp" + string(os.PathSeparator) + GlobalCallerDepth = 4 +) + +var ( + DefaultDirName = filepath.Join("logs", "csp") ) var ( globalLogLevel = InfoLevel - globalLogger = NewConsoleLogger(DefaultNamespace) + globalLogger = NewConsoleLogger() + + FrequentErrorOnce = &sync.Once{} ) func GetGlobalLoggerLevel() Level { @@ -48,185 +57,191 @@ func ResetGlobalLogger(log Logger) error { return nil } -func NewConsoleLogger(namespace string) Logger { +func NewConsoleLogger() Logger { return &DefaultLogger{ - log: log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile), - namespace: namespace, + log: log.New(os.Stdout, "", 0), } } // outputFile is the full path(absolute path) -func NewSimpleFileLogger(filepath, namespace string, flag int) (Logger, error) { +func NewSimpleFileLogger(filepath string) (Logger, error) { logFile, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) if err != nil { return nil, err } return &DefaultLogger{ - log: log.New(logFile, "", flag), - namespace: namespace, - }, err + log: log.New(logFile, "", 0), + }, nil } type Logger interface { - Debug(v ...interface{}) - Debugf(format string, v ...interface{}) + Debug(msg string, keysAndValues ...interface{}) - Info(v ...interface{}) - Infof(format string, v ...interface{}) + // Info logs a non-error message with the given key/value pairs as context. + // + // The msg argument should be used to add some constant description to + // the log line. The key/value pairs can then be used to add additional + // variable information. The key/value pairs should alternate string + // keys and arbitrary values. + Info(msg string, keysAndValues ...interface{}) - Warn(v ...interface{}) - Warnf(format string, v ...interface{}) + Warn(msg string, keysAndValues ...interface{}) - Error(v ...interface{}) - Errorf(format string, v ...interface{}) - - Fatal(v ...interface{}) - Fatalf(format string, v ...interface{}) - - Panic(v ...interface{}) - Panicf(format string, v ...interface{}) + Error(err error, msg string, keysAndValues ...interface{}) } // sentinel general logger type DefaultLogger struct { - // entity to log log *log.Logger - // namespace - namespace string -} - -func merge(namespace, logLevel, msg string) string { - return fmt.Sprintf("[%s] [%s] %s", namespace, logLevel, msg) } -func (l *DefaultLogger) Debug(v ...interface{}) { - if DebugLevel < globalLogLevel || len(v) == 0 { - return +func caller(depth int) (file string, line int) { + _, file, line, ok := runtime.Caller(depth) + if !ok { + file = "???" + line = 0 } - l.log.Print(merge(l.namespace, "DEBUG", fmt.Sprint(v...))) -} -func (l *DefaultLogger) Debugf(format string, v ...interface{}) { - if DebugLevel < globalLogLevel { - return + // extract + if osType := runtime.GOOS; osType == "windows" { + file = strings.ReplaceAll(file, "\\", "/") } - l.log.Print(merge(l.namespace, "DEBUG", fmt.Sprintf(format, v...))) -} - -func (l *DefaultLogger) Info(v ...interface{}) { - if InfoLevel < globalLogLevel { - return + idx := strings.LastIndex(file, "/") + file = file[idx+1:] + return +} + +func AssembleMsg(depth int, logLevel, msg string, err error, keysAndValues ...interface{}) string { + sb := strings.Builder{} + file, line := caller(depth) + timeStr := time.Now().Format("2006-01-02 15:04:05.520") + caller := fmt.Sprintf("%s:%d", file, line) + sb.WriteString("{") + + sb.WriteByte('"') + sb.WriteString("timestamp") + sb.WriteByte('"') + sb.WriteByte(':') + sb.WriteByte('"') + sb.WriteString(timeStr) + sb.WriteByte('"') + sb.WriteByte(',') + + sb.WriteByte('"') + sb.WriteString("caller") + sb.WriteByte('"') + sb.WriteByte(':') + sb.WriteByte('"') + sb.WriteString(caller) + sb.WriteByte('"') + sb.WriteByte(',') + + sb.WriteByte('"') + sb.WriteString("logLevel") + sb.WriteByte('"') + sb.WriteByte(':') + sb.WriteByte('"') + sb.WriteString(logLevel) + sb.WriteByte('"') + sb.WriteByte(',') + + sb.WriteByte('"') + sb.WriteString("msg") + sb.WriteByte('"') + sb.WriteByte(':') + sb.WriteByte('"') + sb.WriteString(msg) + sb.WriteByte('"') + + kvLen := len(keysAndValues) + if kvLen&1 != 0 { + sb.WriteByte(',') + sb.WriteByte('"') + sb.WriteString("kvs") + sb.WriteByte('"') + sb.WriteByte(':') + sb.WriteByte('"') + sb.WriteString(fmt.Sprintf("%+v", keysAndValues)) + sb.WriteByte('"') + } else if kvLen != 0 { + for i := 0; i < kvLen; { + k := keysAndValues[i] + v := keysAndValues[i+1] + kStr, kIsStr := k.(string) + if !kIsStr { + kStr = fmt.Sprintf("%+v", k) + } + sb.WriteByte(',') + sb.WriteByte('"') + sb.WriteString(kStr) + sb.WriteByte('"') + sb.WriteByte(':') + vStr, vIsStr := v.(string) + if !vIsStr { + if vbs, err := json.Marshal(v); err != nil { + sb.WriteByte('"') + sb.WriteString(fmt.Sprintf("%+v", v)) + sb.WriteByte('"') + } else { + sb.WriteString(string(vbs)) + } + } else { + sb.WriteByte('"') + sb.WriteString(vStr) + sb.WriteByte('"') + } + i = i + 2 + } + } + sb.WriteByte('}') + if err != nil { + sb.WriteString("\n") + sb.WriteString(fmt.Sprintf("%+v", err)) } - l.log.Print(merge(l.namespace, "INFO", fmt.Sprint(v...))) + return sb.String() } -func (l *DefaultLogger) Infof(format string, v ...interface{}) { - if InfoLevel < globalLogLevel { +func (l *DefaultLogger) Debug(msg string, keysAndValues ...interface{}) { + if DebugLevel < globalLogLevel { return } - l.log.Print(merge(l.namespace, "INFO", fmt.Sprintf(format, v...))) + l.log.Print(AssembleMsg(GlobalCallerDepth, "DEBUG", msg, nil, keysAndValues...)) } -func (l *DefaultLogger) Warn(v ...interface{}) { - if WarnLevel < globalLogLevel { +func (l *DefaultLogger) Info(msg string, keysAndValues ...interface{}) { + if InfoLevel < globalLogLevel { return } - l.log.Print(merge(l.namespace, "WARNING", fmt.Sprint(v...))) + l.log.Print(AssembleMsg(GlobalCallerDepth, "INFO", msg, nil, keysAndValues...)) } -func (l *DefaultLogger) Warnf(format string, v ...interface{}) { +func (l *DefaultLogger) Warn(msg string, keysAndValues ...interface{}) { if WarnLevel < globalLogLevel { return } - l.log.Print(merge(l.namespace, "WARNING", fmt.Sprintf(format, v...))) -} -func (l *DefaultLogger) Error(v ...interface{}) { - if ErrorLevel < globalLogLevel { - return - } - l.log.Print(merge(l.namespace, "ERROR", fmt.Sprint(v...))) + l.log.Print(AssembleMsg(GlobalCallerDepth, "WARNING", msg, nil, keysAndValues...)) } -func (l *DefaultLogger) Errorf(format string, v ...interface{}) { +func (l *DefaultLogger) Error(err error, msg string, keysAndValues ...interface{}) { if ErrorLevel < globalLogLevel { return } - l.log.Print(merge(l.namespace, "ERROR", fmt.Sprintf(format, v...))) -} - -func (l *DefaultLogger) Fatal(v ...interface{}) { - if FatalLevel < globalLogLevel { - return - } - l.log.Print(merge(l.namespace, "FATAL", fmt.Sprint(v...))) -} - -func (l *DefaultLogger) Fatalf(format string, v ...interface{}) { - if FatalLevel < globalLogLevel { - return - } - l.log.Print(merge(l.namespace, "FATAL", fmt.Sprintf(format, v...))) -} - -func (l *DefaultLogger) Panic(v ...interface{}) { - if PanicLevel < globalLogLevel { - return - } - l.log.Print(merge(l.namespace, "PANIC", fmt.Sprint(v...))) -} - -func (l *DefaultLogger) Panicf(format string, v ...interface{}) { - if PanicLevel < globalLogLevel { - return - } - l.log.Print(merge(l.namespace, "PANIC", fmt.Sprintf(format, v...))) -} - -func Debug(v ...interface{}) { - globalLogger.Debug(v...) -} - -func Debugf(format string, v ...interface{}) { - globalLogger.Debugf(format, v...) -} - -func Info(v ...interface{}) { - globalLogger.Info(v...) -} - -func Infof(format string, v ...interface{}) { - globalLogger.Infof(format, v...) -} - -func Warn(v ...interface{}) { - globalLogger.Warn(v...) -} - -func Warnf(format string, v ...interface{}) { - globalLogger.Warnf(format, v...) -} - -func Error(v ...interface{}) { - globalLogger.Error(v...) -} - -func Errorf(format string, v ...interface{}) { - globalLogger.Errorf(format, v...) + l.log.Print(AssembleMsg(GlobalCallerDepth, "ERROR", msg, err, keysAndValues...)) } -func Fatal(v ...interface{}) { - globalLogger.Fatal(v...) +func Debug(msg string, keysAndValues ...interface{}) { + globalLogger.Debug(msg, keysAndValues...) } -func Fatalf(format string, v ...interface{}) { - globalLogger.Fatalf(format, v...) +func Info(msg string, keysAndValues ...interface{}) { + globalLogger.Info(msg, keysAndValues...) } -func Panic(v ...interface{}) { - globalLogger.Panic(v...) +func Warn(msg string, keysAndValues ...interface{}) { + globalLogger.Warn(msg, keysAndValues...) } -func Panicf(format string, v ...interface{}) { - globalLogger.Panicf(format, v...) +func Error(err error, msg string, keysAndValues ...interface{}) { + globalLogger.Error(err, msg, keysAndValues...) } diff --git a/logging/logging_test.go b/logging/logging_test.go index 88d7b49a0..93ae75ff6 100644 --- a/logging/logging_test.go +++ b/logging/logging_test.go @@ -1,12 +1,12 @@ package logging import ( - "log" "os" "strings" "testing" "time" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -16,12 +16,50 @@ func TestNewSimpleFileLogger(t *testing.T) { if !strings.HasSuffix(tmpDir, string(os.PathSeparator)) { tmpDir = tmpDir + string(os.PathSeparator) } - logger, err := NewSimpleFileLogger(tmpDir+fileName, "test-log", log.LstdFlags|log.LstdFlags) + logger, err := NewSimpleFileLogger(tmpDir + fileName) assert.NoError(t, err) - logger.Debug("debug info test.") - logger.Infof("Hello %s", "sentinel") + logger.Info("info test1.") + logger.Info("info test2.", "name", "sentinel") time.Sleep(time.Second * 1) _ = os.Remove(fileName) } + +func throwError() error { + return errors.New("test error with caller stack") +} + +func Test_caller_path(t *testing.T) { + Error(throwError(), "test error", "k1", "v1") +} + +func Test_AssembleMsg(t *testing.T) { + t.Run("AssembleMsg1", func(t *testing.T) { + got := AssembleMsg(2, "ERROR", "test msg", nil, "k1", "v1") + assert.True(t, strings.Contains(got, `"logLevel":"ERROR","msg":"test msg","k1":"v1"}`)) + }) + + t.Run("AssembleMsg2", func(t *testing.T) { + got := AssembleMsg(2, "INFO", "test msg2", nil, "k1", "v1", "k2", "v2") + assert.True(t, strings.Contains(got, `"logLevel":"INFO","msg":"test msg2","k1":"v1","k2":"v2"}`)) + }) + + t.Run("AssembleMsg2", func(t *testing.T) { + got := AssembleMsg(2, "INFO", "test msg2", nil) + assert.True(t, strings.Contains(got, `"logLevel":"INFO","msg":"test msg2"}`)) + }) + + t.Run("AssembleMsg1", func(t *testing.T) { + got := AssembleMsg(2, "ERROR", "test msg", throwError(), "k1", "v1") + assert.True(t, strings.Contains(got, `"logLevel":"ERROR","msg":"test msg","k1":"v1"}`)) + assert.True(t, strings.Contains(got, `test error with caller stack`)) + }) +} + +func Test_caller(t *testing.T) { + t.Run("caller1", func(t *testing.T) { + file, _ := caller(1) + assert.True(t, strings.Contains(file, "logging_test.go")) + }) +} diff --git a/tests/core/circuitbreaker/circuitbreaker_slot_integration_test.go b/tests/core/circuitbreaker/circuitbreaker_slot_integration_test.go index 7c1cf566c..15d35ceed 100644 --- a/tests/core/circuitbreaker/circuitbreaker_slot_integration_test.go +++ b/tests/core/circuitbreaker/circuitbreaker_slot_integration_test.go @@ -14,7 +14,6 @@ import ( "github.com/alibaba/sentinel-golang/core/circuitbreaker" "github.com/alibaba/sentinel-golang/core/config" "github.com/alibaba/sentinel-golang/logging" - "github.com/alibaba/sentinel-golang/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -25,18 +24,18 @@ type StateChangeListenerMock struct { func (s *StateChangeListenerMock) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) { _ = s.Called(prev, rule) - logging.Debugf("rule.strategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis()) + logging.Debug("transform to closed", "strategy", rule.Strategy, "prevState", prev.String()) return } func (s *StateChangeListenerMock) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) { _ = s.Called(prev, rule, snapshot) - logging.Debugf("rule.strategy: %+v, From %s to Open, snapshot: %.2f, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis()) + logging.Debug("transform to open", "strategy", rule.Strategy, "prevState", prev.String(), "snapshot", snapshot) } func (s *StateChangeListenerMock) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) { _ = s.Called(prev, rule) - logging.Debugf("rule.strategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis()) + logging.Debug("transform to Half-Open", "strategy", rule.Strategy, "prevState", prev.String()) } // Test scenario @@ -53,7 +52,7 @@ func TestCircuitBreakerSlotIntegration_Normal(t *testing.T) { } conf := config.NewDefaultConfig() - conf.Sentinel.Log.Logger = logging.NewConsoleLogger("cb-integration-normal") + conf.Sentinel.Log.Logger = logging.NewConsoleLogger() err := sentinel.InitWithConfig(conf) if err != nil { t.Fatal(err) @@ -112,7 +111,7 @@ func TestCircuitBreakerSlotIntegration_Normal(t *testing.T) { stateListener2.On("OnTransformToClosed", mock.Anything, mock.Anything).Return() stateListener2.On("OnTransformToOpen", circuitbreaker.HalfOpen, mock.Anything, mock.Anything).Return() stateListener2.On("OnTransformToHalfOpen", circuitbreaker.Open, mock.Anything).Return() - e, b = sentinel.Entry("abc", sentinel.WithSlotChain(sc)) + _, b = sentinel.Entry("abc", sentinel.WithSlotChain(sc)) assert.True(t, b != nil && b.BlockType() == base.BlockTypeCircuitBreaking && b.TriggeredRule().(*circuitbreaker.Rule) == cbRule2) stateListener2.AssertNumberOfCalls(t, "OnTransformToHalfOpen", 1) stateListener2.AssertCalled(t, "OnTransformToHalfOpen", circuitbreaker.Open, mock.Anything) @@ -127,7 +126,7 @@ func TestCircuitBreakerSlotIntegration_Normal(t *testing.T) { stateListener3.On("OnTransformToClosed", mock.Anything, mock.Anything).Return() stateListener3.On("OnTransformToOpen", circuitbreaker.HalfOpen, mock.Anything, mock.Anything).Return() stateListener3.On("OnTransformToHalfOpen", circuitbreaker.Open, mock.Anything).Return() - e, b = sentinel.Entry("abc", sentinel.WithSlotChain(sc)) + _, b = sentinel.Entry("abc", sentinel.WithSlotChain(sc)) assert.True(t, b != nil && b.BlockType() == base.BlockTypeCircuitBreaking && b.TriggeredRule().(*circuitbreaker.Rule) == cbRule2) stateListener3.AssertNumberOfCalls(t, "OnTransformToHalfOpen", 1) stateListener3.AssertCalled(t, "OnTransformToHalfOpen", circuitbreaker.Open, mock.Anything) @@ -147,7 +146,7 @@ func TestCircuitBreakerSlotIntegration_Probe_Succeed(t *testing.T) { } conf := config.NewDefaultConfig() - conf.Sentinel.Log.Logger = logging.NewConsoleLogger("cb-integration-probe-succeed") + conf.Sentinel.Log.Logger = logging.NewConsoleLogger() err := sentinel.InitWithConfig(conf) if err != nil { t.Fatal(err) @@ -218,7 +217,7 @@ func TestCircuitBreakerSlotIntegration_Concurrency(t *testing.T) { t.Fatal(clearErr) } conf := config.NewDefaultConfig() - conf.Sentinel.Log.Logger = logging.NewConsoleLogger("cb-integration-concurrency") + conf.Sentinel.Log.Logger = logging.NewConsoleLogger() err := sentinel.InitWithConfig(conf) if err != nil { t.Fatal(err) diff --git a/tests/testdata/extension/SystemRule.json b/tests/testdata/extension/SystemRule.json index 9740cbb5b..523090704 100644 --- a/tests/testdata/extension/SystemRule.json +++ b/tests/testdata/extension/SystemRule.json @@ -1,16 +1,16 @@ [ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 2, + "id": "2", "metricType": 0, "adaptiveStrategy": 0 } diff --git a/tests/testdata/extension/SystemRule2.json b/tests/testdata/extension/SystemRule2.json index 9740cbb5b..523090704 100644 --- a/tests/testdata/extension/SystemRule2.json +++ b/tests/testdata/extension/SystemRule2.json @@ -1,16 +1,16 @@ [ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 2, + "id": "2", "metricType": 0, "adaptiveStrategy": 0 } diff --git a/tests/testdata/extension/SystemRule3.json b/tests/testdata/extension/SystemRule3.json index e5740065a..d58f5d96c 100644 --- a/tests/testdata/extension/SystemRule3.json +++ b/tests/testdata/extension/SystemRule3.json @@ -1,11 +1,11 @@ [ { - "id": 0, + "id": "0", "metricType": 0, "adaptiveStrategy": 0 }, { - "id": 1, + "id": "1", "metricType": 0, "adaptiveStrategy": 0 } diff --git a/tests/testdata/extension/helper/FlowRule.json b/tests/testdata/extension/helper/FlowRule.json index ee75e4c0e..80baa98b1 100644 --- a/tests/testdata/extension/helper/FlowRule.json +++ b/tests/testdata/extension/helper/FlowRule.json @@ -1,47 +1,35 @@ [ { "resource": "abc", - "limitOrigin": "default", - "metricType": 0, - "count": 100.0, + "threshold": 100.0, "relationStrategy": 0, + "tokenCalculateStrategy": 0, "controlBehavior": 0, "refResource": "refDefault", "warmUpPeriodSec": 10, "maxQueueingTimeMs": 1000, - "clusterMode": false, - "clusterConfig": { - "thresholdType": 0 - } + "statIntervalInMs": 0 }, { "resource": "abc", - "limitOrigin": "default", - "metricType": 1, - "count": 200.0, + "threshold": 200.0, "relationStrategy": 1, + "tokenCalculateStrategy": 0, "controlBehavior": 1, "refResource": "refDefault", "warmUpPeriodSec": 20, "maxQueueingTimeMs": 2000, - "clusterMode": true, - "clusterConfig": { - "thresholdType": 1 - } + "statIntervalInMs": 0 }, { "resource": "abc", - "limitOrigin": "default", - "metricType": 1, - "count": 300.0, + "threshold": 300.0, "relationStrategy": 0, + "tokenCalculateStrategy": 0, "controlBehavior": 1, "refResource": "refDefault", "warmUpPeriodSec": 30, "maxQueueingTimeMs": 3000, - "clusterMode": true, - "clusterConfig": { - "thresholdType": 1 - } + "statIntervalInMs": 0 } ] \ No newline at end of file diff --git a/util/auto_recover.go b/util/auto_recover.go index 05efbb051..417150dfc 100644 --- a/util/auto_recover.go +++ b/util/auto_recover.go @@ -8,7 +8,7 @@ import ( func RunWithRecover(f func()) { defer func() { if err := recover(); err != nil { - logging.Panicf("Unexpected panic: %+v", errors.Errorf("%+v", err)) + logging.Error(errors.Errorf("%+v", err), "unexpected panic") } }() f() diff --git a/util/file.go b/util/file.go index e8aec509f..73fcc9c4b 100644 --- a/util/file.go +++ b/util/file.go @@ -4,7 +4,6 @@ import ( "errors" "io" "os" - "strings" ) func FilePosition(file *os.File) (int64, error) { @@ -34,11 +33,3 @@ func CreateDirIfNotExists(dirname string) error { } return nil } - -func AddPathSeparatorIfAbsent(path string) string { - s := string(os.PathSeparator) - if !strings.HasSuffix(path, s) { - return path + s - } - return path -}