Skip to content

Commit

Permalink
feat: sentinel plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
WeixinX committed Sep 23, 2024
1 parent cbf3349 commit b581d1b
Show file tree
Hide file tree
Showing 12 changed files with 1,759 additions and 0 deletions.
8 changes: 8 additions & 0 deletions plugins/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ go 1.21.5

require (
github.com/agiledragon/gomonkey/v2 v2.11.0
github.com/alibaba/sentinel-golang v1.0.4
github.com/avast/retry-go v3.0.0+incompatible
github.com/casbin/casbin/v2 v2.88.0
github.com/coreos/go-oidc/v3 v3.10.0
Expand All @@ -44,6 +45,7 @@ require (
require (
cel.dev/expr v0.15.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand All @@ -60,22 +62,28 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/shirou/gopsutil/v3 v3.21.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/tklauser/go-sysconf v0.3.6 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
Expand Down
386 changes: 386 additions & 0 deletions plugins/go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ import (
_ "mosn.io/htnn/plugins/plugins/limitreq"
_ "mosn.io/htnn/plugins/plugins/oidc"
_ "mosn.io/htnn/plugins/plugins/opa"
_ "mosn.io/htnn/plugins/plugins/sentinel"
)
105 changes: 105 additions & 0 deletions plugins/plugins/sentinel/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright The HTNN Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sentinel

import (
sentinelApi "github.com/alibaba/sentinel-golang/api"
sentinelConf "github.com/alibaba/sentinel-golang/core/config"
"github.com/alibaba/sentinel-golang/logging"

"mosn.io/htnn/api/pkg/filtermanager/api"
"mosn.io/htnn/api/pkg/plugins"
"mosn.io/htnn/plugins/plugins/sentinel/rules"
"mosn.io/htnn/types/plugins/sentinel"
)

func init() {
plugins.RegisterPlugin(sentinel.Name, &plugin{})
}

type plugin struct {
sentinel.Plugin
}

func (p *plugin) Factory() api.FilterFactory {
return factory
}

func (p *plugin) Config() api.PluginConfig {
return &config{}
}

type config struct {
sentinel.CustomConfig

params sentinelApi.EntryOption
attachments []*sentinel.Source
m *res2RuleMap
}

type res2RuleMap struct {
f map[string]*sentinel.FlowRule
hs map[string]*sentinel.HotSpotRule
cb map[string]*sentinel.CircuitBreakerRule
}

func (conf *config) Init(cb api.ConfigCallbackHandler) error {
sc := sentinelConf.NewDefaultConfig()
sc.Sentinel.Log.Logger = logging.NewConsoleLogger()
sc.Sentinel.Log.Dir = "/tmp/sentinel"
if err := sentinelApi.InitWithConfig(sc); err != nil {
return err

Check warning on line 63 in plugins/plugins/sentinel/config.go

View check run for this annotation

Codecov / codecov/patch

plugins/plugins/sentinel/config.go#L63

Added line #L63 was not covered by tests
}

if _, err := loadRules(conf); err != nil {
return err

Check warning on line 67 in plugins/plugins/sentinel/config.go

View check run for this annotation

Codecov / codecov/patch

plugins/plugins/sentinel/config.go#L67

Added line #L67 was not covered by tests
}

return nil
}

func loadRules(conf *config) (bool, error) {
conf.m = &res2RuleMap{
f: make(map[string]*sentinel.FlowRule),
hs: make(map[string]*sentinel.HotSpotRule),
cb: make(map[string]*sentinel.CircuitBreakerRule),
}

conf.params = sentinelApi.WithArgs()
conf.attachments = make([]*sentinel.Source, 0)
hs := conf.GetHotSpot()
if hs != nil {
args := make([]interface{}, len(hs.GetParams()))
for i, p := range hs.GetParams() {
args[i] = p
}
conf.params = sentinelApi.WithArgs(args...)
conf.attachments = hs.GetAttachments()
}

if ok, err := rules.LoadFlowRules(conf.GetFlow(), conf.m.f); !ok || err != nil {
return ok, err

Check warning on line 93 in plugins/plugins/sentinel/config.go

View check run for this annotation

Codecov / codecov/patch

plugins/plugins/sentinel/config.go#L93

Added line #L93 was not covered by tests
}

if ok, err := rules.LoadHotSpotRules(hs, conf.m.hs); !ok || err != nil {
return ok, err

Check warning on line 97 in plugins/plugins/sentinel/config.go

View check run for this annotation

Codecov / codecov/patch

plugins/plugins/sentinel/config.go#L97

Added line #L97 was not covered by tests
}

if ok, err := rules.LoadCircuitBreakerRules(conf.GetCircuitBreaker(), conf.m.cb); !ok || err != nil {
return ok, err

Check warning on line 101 in plugins/plugins/sentinel/config.go

View check run for this annotation

Codecov / codecov/patch

plugins/plugins/sentinel/config.go#L101

Added line #L101 was not covered by tests
}

return true, nil
}
101 changes: 101 additions & 0 deletions plugins/plugins/sentinel/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright The HTNN Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sentinel

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/encoding/protojson"
)

func TestConfig(t *testing.T) {
tests := []struct {
name string
input string
err string
}{
{
name: "resource are required",
input: `{}`,
err: "invalid Config.Resource: value is required",
},
{
name: "one of flow, hotSpot, circuitBreaker is required",
input: `{"resource": {"from": "HEADER", "key": "test"}}`,
err: "config must have at least one of 'flow', 'hotSpot', 'circuitBreaker'",
},
{
name: "flow err: threshold must be greater than 0",
input: `{"resource": {"from": "HEADER", "key": "test"}, "flow": {"rules": [{"resource": "flow"}]}}`,
err: "'threshold' must be greater than 0",
},
{
name: "flow ok: current resource",
input: `{"resource": {"from": "HEADER", "key": "test"}, "flow": {"rules": [{"resource": "flow", "threshold": 10}]}}`,
err: "",
},
{
name: "flow ok: related resource",
input: `{"resource": {"from": "HEADER", "key": "test"}, "flow": {"rules": [{"resource": "f2", "relationStrategy": "ASSOCIATED_RESOURCE", "refResource": "f1"}]}}`,
err: "",
},
{
name: "hot spot err: one of params, attachments is required",
input: `{"resource": {"from": "HEADER", "key": "test"}, "hotSpot": {}}`,
err: "'params' and 'attachments' cannot both be empty",
},
{
name: "hot spot err: threshold must be greater than 0",
input: `{"resource": {"from": "HEADER", "key": "test"}, "hotSpot": {"params": ["test"], "rules": [{"resource": "hs"}]}}`,
err: "invalid HotSpotRule.Threshold: value must be greater than 0",
},
{
name: "hot spot ok",
input: `{"resource": {"from": "HEADER", "key": "test"}, "hotSpot": {"params": ["test"], "rules": [{"resource": "hs", "metricType": "QPS", "threshold": 10}]}}`,
err: "",
},
{
name: "circuit breaker err: threshold must be greater than 0",
input: `{"resource": {"from": "HEADER", "key": "test"}, "circuitBreaker": {"rules": [{"resource": "cb"}]}}`,
err: "invalid CircuitBreakerRule.Threshold: value must be greater than 0",
},
{
name: "circuit breaker ok",
input: `{"resource": {"from": "HEADER", "key": "test"}, "circuitBreaker": {"rules": [{"resource": "cb", "threshold": 10}]}}`,
err: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := &config{}
err := protojson.Unmarshal([]byte(tt.input), conf)
if err == nil {
err = conf.Validate()
}
if tt.err == "" {
assert.Nil(t, err)
err = conf.Init(nil)
assert.Nil(t, err)
} else {
fmt.Println(err)
assert.ErrorContains(t, err, tt.err)
}
})
}

}
Loading

0 comments on commit b581d1b

Please sign in to comment.