Sentinel middleware support (#829)
* a prototype for sentinel middleware * adapt sentinel's logger to dapr and optimize configs * fix lint issue * fix lint issues * go mod tidy * revise accoring to the review comments * go mod tidy * enhance unit tests * fix lint issue * fix go.sum Co-authored-by: Phil Kedy <phil.kedy@gmail.com> Co-authored-by: Artur Souza <artursouza.ms@outlook.com>
This commit is contained in:
parent
448bf2b261
commit
55ee03a5a0
3
go.mod
3
go.mod
|
|
@ -18,10 +18,12 @@ require (
|
|||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/Shopify/sarama v1.23.1
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905
|
||||
github.com/aerospike/aerospike-client-go v4.5.0+incompatible
|
||||
github.com/agrea/ptr v0.0.0-20180711073057-77a518d99b7b
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alibaba/sentinel-golang v1.0.2
|
||||
github.com/alicebob/miniredis/v2 v2.13.3
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible
|
||||
github.com/apache/pulsar-client-go v0.1.0
|
||||
|
|
@ -45,6 +47,7 @@ require (
|
|||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible // indirect
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/go-redis/redis/v8 v8.8.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gocql/gocql v0.0.0-20191018090344-07ace3bab0f8
|
||||
|
|
|
|||
18
go.sum
18
go.sum
|
|
@ -124,6 +124,8 @@ github.com/Shopify/sarama v1.23.1 h1:XxJBCZEoWJtoWjf/xRbmGUpAmTZGnuuF0ON0EvxxBrs
|
|||
github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905 h1:lrOYmNobGcyWEjvMIMJERJx1Y4ttPFobY7RHAD+6e10=
|
||||
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905/go.mod h1:4Z0mpi7fkyqjxUdGiNMO3vagyiUoiwLncaIX6AsW5z0=
|
||||
github.com/aerospike/aerospike-client-go v4.5.0+incompatible h1:6ALev/Ge4jW5avSLoqgvPYTh+FLeeDD9xDhzoMCNgOo=
|
||||
|
|
@ -136,6 +138,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alibaba/sentinel-golang v1.0.2 h1:Acopq74hOtZN4MV1v811MQ6QcqPFLDSczTrRXv9zpIg=
|
||||
github.com/alibaba/sentinel-golang v1.0.2/go.mod h1:QsB99f/z35D2AiMrAWwgWE85kDTkBUIkcmPrRt+61NI=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.13.3 h1:kohgdtN58KW/r9ZDVmMJE3MrfbumwsDQStd0LPAGmmw=
|
||||
|
|
@ -315,6 +319,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
|||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
|
|
@ -828,6 +834,8 @@ github.com/sendgrid/sendgrid-go v3.5.0+incompatible h1:kosbgHyNVYVaqECDYvFVLVD9n
|
|||
github.com/sendgrid/sendgrid-go v3.5.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto=
|
||||
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
|
|
@ -947,8 +955,14 @@ go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz
|
|||
go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
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.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
|
|
@ -1104,6 +1118,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -1181,6 +1196,8 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20190920225731-5eefd052ad72/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/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
|
@ -1381,6 +1398,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/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.20.0 h1:WwrYoZNM1W1aQEbyl8HNG+oWGzLpZQBlcerS9BQw9yI=
|
||||
k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package sentinel
|
||||
|
||||
import (
|
||||
"github.com/alibaba/sentinel-golang/logging"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
type loggerAdaptor struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) Debug(msg string, keysAndValues ...interface{}) {
|
||||
s := logging.AssembleMsg(logging.GlobalCallerDepth, "DEBUG", msg, nil, keysAndValues...)
|
||||
l.logger.Debug(s)
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) DebugEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) Info(msg string, keysAndValues ...interface{}) {
|
||||
s := logging.AssembleMsg(logging.GlobalCallerDepth, "INFO", msg, nil, keysAndValues...)
|
||||
l.logger.Info(s)
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) InfoEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) Warn(msg string, keysAndValues ...interface{}) {
|
||||
s := logging.AssembleMsg(logging.GlobalCallerDepth, "WARNING", msg, nil, keysAndValues...)
|
||||
l.logger.Info(s)
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) WarnEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||
s := logging.AssembleMsg(logging.GlobalCallerDepth, "ERROR", msg, nil, keysAndValues...)
|
||||
l.logger.Info(s)
|
||||
}
|
||||
|
||||
func (l *loggerAdaptor) ErrorEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation and Dapr Contributors.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
package sentinel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
sentinel "github.com/alibaba/sentinel-golang/api"
|
||||
"github.com/alibaba/sentinel-golang/core/base"
|
||||
"github.com/alibaba/sentinel-golang/core/config"
|
||||
"github.com/dapr/components-contrib/middleware"
|
||||
"github.com/dapr/kit/logger"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type middlewareMetadata struct {
|
||||
AppName string `json:"appName"`
|
||||
// LogConfig
|
||||
LogDir string `json:"logDir"`
|
||||
// Rules
|
||||
FlowRules string `yaml:"flowRules"`
|
||||
CircuitBreakerRules string `yaml:"circuitBreakerRules"`
|
||||
HotSpotParamRules string `yaml:"hotSpotParamRules"`
|
||||
IsolationRules string `yaml:"isolationRules"`
|
||||
SystemRules string `yaml:"systemRules"`
|
||||
}
|
||||
|
||||
// NewMiddleware returns a new sentinel middleware
|
||||
func NewMiddleware(logger logger.Logger) *Middleware {
|
||||
return &Middleware{logger: logger}
|
||||
}
|
||||
|
||||
// Middleware is an sentinel middleware
|
||||
type Middleware struct {
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// GetHandler returns the HTTP handler provided by sentinel middleware
|
||||
func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) {
|
||||
var (
|
||||
meta *middlewareMetadata
|
||||
err error
|
||||
)
|
||||
|
||||
meta, err = getNativeMetadata(metadata)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error to parse sentinel metadata")
|
||||
}
|
||||
|
||||
conf := m.newSentinelConfig(meta)
|
||||
err = sentinel.InitWithConfig(conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error to init sentinel with config: %s", conf)
|
||||
}
|
||||
|
||||
err = m.loadSentinelRules(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
resourceName := string(ctx.Method()) + ":" + string(ctx.Path())
|
||||
entry, err := sentinel.Entry(
|
||||
resourceName,
|
||||
sentinel.WithResourceType(base.ResTypeWeb),
|
||||
sentinel.WithTrafficType(base.Inbound),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusTooManyRequests), fasthttp.StatusTooManyRequests)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer entry.Exit()
|
||||
h(ctx)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Middleware) loadSentinelRules(meta *middlewareMetadata) error {
|
||||
if meta.FlowRules != "" {
|
||||
err := loadRules(meta.FlowRules, newFlowRuleDataSource)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to load sentinel flow rules: %s", meta.FlowRules)
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if meta.IsolationRules != "" {
|
||||
err := loadRules(meta.IsolationRules, newIsolationRuleDataSource)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to load sentinel isolation rules: %s", meta.IsolationRules)
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if meta.CircuitBreakerRules != "" {
|
||||
err := loadRules(meta.CircuitBreakerRules, newCircuitBreakerRuleDataSource)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to load sentinel circuit breaker rules: %s", meta.CircuitBreakerRules)
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if meta.HotSpotParamRules != "" {
|
||||
err := loadRules(meta.HotSpotParamRules, newHotSpotParamRuleDataSource)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to load sentinel hotspot param rules: %s", meta.HotSpotParamRules)
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
if meta.SystemRules != "" {
|
||||
err := loadRules(meta.SystemRules, newSystemRuleDataSource)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to load sentinel system rules: %s", meta.SystemRules)
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Middleware) newSentinelConfig(metadata *middlewareMetadata) *config.Entity {
|
||||
conf := config.NewDefaultConfig()
|
||||
|
||||
if metadata.AppName != "" {
|
||||
conf.Sentinel.App.Name = metadata.AppName
|
||||
}
|
||||
|
||||
if metadata.LogDir != "" {
|
||||
conf.Sentinel.Log.Dir = metadata.LogDir
|
||||
}
|
||||
|
||||
conf.Sentinel.Log.Logger = &loggerAdaptor{m.logger}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error) {
|
||||
b, err := json.Marshal(metadata.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var md middlewareMetadata
|
||||
err = json.Unmarshal(b, &md)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &md, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package sentinel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dapr/components-contrib/middleware"
|
||||
"github.com/dapr/kit/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type counter struct {
|
||||
count int32
|
||||
}
|
||||
|
||||
func (c *counter) handle(ctx *fasthttp.RequestCtx) {
|
||||
c.count++
|
||||
}
|
||||
|
||||
func TestRequestHandlerWithFlowRules(t *testing.T) {
|
||||
meta := middleware.Metadata{Properties: map[string]string{
|
||||
"appName": "test-app",
|
||||
"flowRules": `[
|
||||
{
|
||||
"resource": "GET:/v1.0/nodeapp/healthz",
|
||||
"threshold": 10,
|
||||
"tokenCalculateStrategy": 0,
|
||||
"controlBehavior": 0
|
||||
}
|
||||
]`,
|
||||
}}
|
||||
|
||||
log := logger.NewLogger("sentinel.test")
|
||||
sentinel := NewMiddleware(log)
|
||||
handler, err := sentinel.GetHandler(meta)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var ctx fasthttp.RequestCtx
|
||||
ctx.Request.SetHost("localhost:5001")
|
||||
ctx.Request.SetRequestURI("/v1.0/nodeapp/healthz")
|
||||
ctx.Request.Header.SetMethod("GET")
|
||||
|
||||
counter := &counter{}
|
||||
for i := 0; i < 100; i++ {
|
||||
handler(counter.handle)(&ctx)
|
||||
}
|
||||
|
||||
assert.Equal(t, int32(10), counter.count)
|
||||
}
|
||||
|
||||
func TestLoadRules(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
meta middlewareMetadata
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid flow rules but return no error",
|
||||
meta: middlewareMetadata{
|
||||
AppName: "nodeapp",
|
||||
FlowRules: `[
|
||||
{
|
||||
"resource": "GET:/v1.0/nodeapp/healthz",
|
||||
"strategy": 1,
|
||||
"statIntervalInMs": -1
|
||||
}
|
||||
]`,
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid circuit breaker rules and return error",
|
||||
meta: middlewareMetadata{
|
||||
AppName: "nodeapp",
|
||||
CircuitBreakerRules: `[
|
||||
{
|
||||
"resource": "GET:/v1.0/nodeapp/healthz",
|
||||
"strategy": 1,
|
||||
"not-existing-property": -1
|
||||
}
|
||||
]`,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid hotspot rules and return no error",
|
||||
meta: middlewareMetadata{
|
||||
AppName: "nodeapp",
|
||||
HotSpotParamRules: `[
|
||||
{
|
||||
"resource": "GET:/v1.0/nodeapp/healthz",
|
||||
"metricType": 1,
|
||||
"not-existing-property": -1
|
||||
}
|
||||
]`,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid system rules and return no error",
|
||||
meta: middlewareMetadata{
|
||||
AppName: "nodeapp",
|
||||
SystemRules: `[
|
||||
{
|
||||
}
|
||||
]`,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
sentinel := NewMiddleware(nil)
|
||||
err := sentinel.loadSentinelRules(&c.meta)
|
||||
if c.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation and Dapr Contributors.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
package sentinel
|
||||
|
||||
import (
|
||||
"github.com/alibaba/sentinel-golang/ext/datasource"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type propertyDataSource struct {
|
||||
datasource.Base
|
||||
rules string
|
||||
}
|
||||
|
||||
func loadRules(rules string, newDatasource func(rules string) (datasource.DataSource, error)) error {
|
||||
if rules != "" {
|
||||
ds, err := newDatasource(rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ds.Initialize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFlowRuleDataSource(rules string) (datasource.DataSource, error) {
|
||||
return newDataSource(rules, datasource.NewFlowRulesHandler(datasource.FlowRuleJsonArrayParser))
|
||||
}
|
||||
|
||||
func newCircuitBreakerRuleDataSource(rules string) (datasource.DataSource, error) {
|
||||
return newDataSource(rules, datasource.NewCircuitBreakerRulesHandler(datasource.CircuitBreakerRuleJsonArrayParser))
|
||||
}
|
||||
|
||||
func newHotSpotParamRuleDataSource(rules string) (datasource.DataSource, error) {
|
||||
return newDataSource(rules, datasource.NewHotSpotParamRulesHandler(datasource.HotSpotParamRuleJsonArrayParser))
|
||||
}
|
||||
|
||||
func newIsolationRuleDataSource(rules string) (datasource.DataSource, error) {
|
||||
return newDataSource(rules, datasource.NewIsolationRulesHandler(datasource.IsolationRuleJsonArrayParser))
|
||||
}
|
||||
|
||||
func newSystemRuleDataSource(rules string) (datasource.DataSource, error) {
|
||||
return newDataSource(rules, datasource.NewSystemRulesHandler(datasource.SystemRuleJsonArrayParser))
|
||||
}
|
||||
|
||||
func newDataSource(rules string, handlers ...datasource.PropertyHandler) (datasource.DataSource, error) {
|
||||
ds := &propertyDataSource{
|
||||
rules: rules,
|
||||
}
|
||||
for _, h := range handlers {
|
||||
ds.AddPropertyHandler(h)
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func (p propertyDataSource) ReadSource() ([]byte, error) {
|
||||
return []byte(p.rules), nil
|
||||
}
|
||||
|
||||
func (p propertyDataSource) Initialize() error {
|
||||
src, err := p.ReadSource()
|
||||
if err != nil {
|
||||
err = errors.Errorf("Fail to read source, err: %+v", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Handle(src)
|
||||
}
|
||||
|
||||
func (p propertyDataSource) Close() error {
|
||||
// no op
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package sentinel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/sentinel-golang/core/circuitbreaker"
|
||||
"github.com/alibaba/sentinel-golang/core/flow"
|
||||
"github.com/alibaba/sentinel-golang/core/isolation"
|
||||
"github.com/alibaba/sentinel-golang/core/system"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFlowRules(t *testing.T) {
|
||||
rules := []*flow.Rule{
|
||||
{
|
||||
Resource: "some-test",
|
||||
Threshold: 100,
|
||||
TokenCalculateStrategy: flow.Direct,
|
||||
ControlBehavior: flow.Reject,
|
||||
},
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(rules)
|
||||
t.Logf("%s", b)
|
||||
err := loadRules(string(b), newFlowRuleDataSource)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestCircuitBreakerRules(t *testing.T) {
|
||||
rules := []*circuitbreaker.Rule{
|
||||
{
|
||||
Resource: "abc",
|
||||
Strategy: circuitbreaker.ErrorCount,
|
||||
RetryTimeoutMs: 3000,
|
||||
MinRequestAmount: 10,
|
||||
StatIntervalMs: 5000,
|
||||
Threshold: 50,
|
||||
},
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(rules)
|
||||
t.Logf("%s", b)
|
||||
err := loadRules(string(b), newCircuitBreakerRuleDataSource)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestHotspotParamRules(t *testing.T) {
|
||||
rules := `
|
||||
[
|
||||
{
|
||||
"resource": "abc",
|
||||
"metricType": 1,
|
||||
"controlBehavior": 0,
|
||||
"paramIndex": 1,
|
||||
"threshold": 50,
|
||||
"burstCount": 0,
|
||||
"durationInSec": 1
|
||||
}
|
||||
]
|
||||
`
|
||||
err := loadRules(rules, newHotSpotParamRuleDataSource)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestIsolationRules(t *testing.T) {
|
||||
rules := []*isolation.Rule{
|
||||
{
|
||||
Resource: "abc",
|
||||
MetricType: isolation.Concurrency,
|
||||
Threshold: 12,
|
||||
},
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(rules)
|
||||
t.Logf("%s", b)
|
||||
err := loadRules(string(b), newIsolationRuleDataSource)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestSystemRules(t *testing.T) {
|
||||
rules := []*system.Rule{
|
||||
{
|
||||
ID: "test-id",
|
||||
MetricType: system.InboundQPS,
|
||||
TriggerCount: 1000,
|
||||
Strategy: system.BBR,
|
||||
},
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(rules)
|
||||
t.Logf("%s", b)
|
||||
err := loadRules(string(b), newSystemRuleDataSource)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
Loading…
Reference in New Issue