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:
Ian Luo 2021-05-14 03:11:24 +08:00 committed by GitHub
parent 448bf2b261
commit 55ee03a5a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 533 additions and 0 deletions

3
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}