Skip to content

Commit

Permalink
feat: go agent
Browse files Browse the repository at this point in the history
- add fallback rule
- support fallback for gin and go-zero web
- support mock for system rule
  • Loading branch information
EndlessSeeker committed Jun 26, 2024
1 parent 0807185 commit 62502e7
Show file tree
Hide file tree
Showing 19 changed files with 5,765 additions and 337 deletions.
53 changes: 53 additions & 0 deletions core/fallback/rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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 fallback

type TargetResourceType int64

const (
WebResourceType TargetResourceType = 1
RpcResourceType TargetResourceType = 2
)

type FunctionType int64

const (
FlowType FunctionType = 1
Isolation FunctionType = 6
HotspotRpc FunctionType = 4
HotspotHttp FunctionType = 11
)

type Rule struct {
TargetResourceType TargetResourceType `json:"targetResourceType"`
TargetMap map[string][]FunctionType `json:"targetMap"`
FallbackBehavior interface{} `json:"fallbackBehavior"`
}

type WebBlockFallbackBehavior struct {
WebFallbackMode int64 `json:"webFallbackMode"` // 0: return, 1: redirect
WebRespStatusCode int64 `json:"webRespStatusCode"`
WebRespMessage string `json:"webRespMessage"`
WebRespContentType int64 `json:"webRespContentType"` // 0: test, 1: json
WebRedirectUrl string `json:"webRedirectUrl"`
}

type RpcBlockFallbackBehavior struct {
RpcFallbackMode int64 `json:"rpcFallbackMode"`
RpcFallbackCacheMode int64 `json:"rpcFallbackCacheMode"`
RpcRespFallbackClassName string `json:"rpcRespFallbackClassName"`
RpcFallbackExceptionMessage string `json:"rpcFallbackExceptionMessage"`
RpcRespContentBody string `json:"rpcRespContentBody"`
}
174 changes: 174 additions & 0 deletions core/fallback/rule_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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 fallback

import (
"encoding/json"
"github.com/alibaba/sentinel-golang/logging"
"github.com/alibaba/sentinel-golang/util"
"reflect"
"sync"
)

var (
webRuleMap = make(map[string]map[FunctionType]*WebBlockFallbackBehavior)
rpcRuleMap = make(map[string]map[FunctionType]*RpcBlockFallbackBehavior)
currentWebRules = make(map[string]map[FunctionType]*WebBlockFallbackBehavior)
currentRpcRules = make(map[string]map[FunctionType]*RpcBlockFallbackBehavior)
webRwMux = &sync.RWMutex{}
rpcRwMux = &sync.RWMutex{}
updateRuleMux = &sync.RWMutex{}
)

func isValidWebFallbackBehavior(behavior *WebBlockFallbackBehavior) bool {
if behavior == nil {
return false
}
if behavior.WebRespContentType != 0 && behavior.WebRespContentType != 1 {
return false
}
if behavior.WebRespStatusCode < 0 || behavior.WebRespStatusCode > 600 {
return false
}
return true
}

func isValidRpcFallbackBehavior(behavior *RpcBlockFallbackBehavior) bool {
if behavior == nil {
return false
}
if behavior.RpcFallbackMode != 0 && behavior.RpcFallbackMode != 1 {
return false
}
return true
}

func LoadRules(rules []*Rule) (bool, error) {
resWebRuleMap := make(map[string]map[FunctionType]*WebBlockFallbackBehavior)
resRpcRuleMap := make(map[string]map[FunctionType]*RpcBlockFallbackBehavior)
for _, rule := range rules {
b, err := json.Marshal(rule.FallbackBehavior)
if err != nil {
logging.Warn("[Fallback] marshal web fall back behavior failed", "reason", err.Error())
continue
}
switch rule.TargetResourceType {
case WebResourceType:
var webBehavior *WebBlockFallbackBehavior
err := json.Unmarshal(b, &webBehavior)
if err != nil {
logging.Warn("[Fallback] unmarshal web fall back behavior failed", "reason", err.Error())
continue
}
if !isValidWebFallbackBehavior(webBehavior) {
logging.Warn("[Fallback] invalid web fall back behavior", "behavior", webBehavior)
continue
}

for resource, funcTypeList := range rule.TargetMap {
if resource == "" || len(funcTypeList) == 0 {
continue
}
var behaviorMap map[FunctionType]*WebBlockFallbackBehavior
var ok bool
if behaviorMap, ok = resWebRuleMap[resource]; !ok {
behaviorMap = make(map[FunctionType]*WebBlockFallbackBehavior)
resWebRuleMap[resource] = behaviorMap
}

for _, functionType := range funcTypeList {
behaviorMap[functionType] = webBehavior
}
}
case RpcResourceType:
var rpcBehavior *RpcBlockFallbackBehavior
err := json.Unmarshal(b, &rpcBehavior)
if err != nil {
logging.Warn("[Fallback] unmarshal rpc fall back behavior failed", "reason", err.Error())
continue
}
if !isValidRpcFallbackBehavior(rpcBehavior) {
logging.Warn("[Fallback] invalid rpc fall back behavior", "behavior", rpcBehavior)
continue
}

for resource, funcTypeList := range rule.TargetMap {
var behaviorMap map[FunctionType]*RpcBlockFallbackBehavior
var ok bool
if behaviorMap, ok = resRpcRuleMap[resource]; !ok {
behaviorMap = make(map[FunctionType]*RpcBlockFallbackBehavior)
resRpcRuleMap[resource] = behaviorMap
}

for _, functionType := range funcTypeList {
behaviorMap[functionType] = rpcBehavior
}
}
default:
logging.Warn("[Fallback] unsupported resource type", "resourceType", rule.TargetResourceType)
continue
}
}

updateRuleMux.Lock()
defer updateRuleMux.Unlock()
var err error
var updated bool
isEqual := reflect.DeepEqual(currentWebRules, resWebRuleMap)
if !isEqual {
updateErr := onWebRuleUpdate(resWebRuleMap)
if updateErr != nil {
logging.Error(updateErr, "[Fallback] update web rule failed")
err = updateErr
} else {
updated = true
}
} else {
logging.Info("[Fallback] Web load rules is the same with current rules, so ignore load operation.")
}
isEqual = reflect.DeepEqual(currentRpcRules, resRpcRuleMap)
if !isEqual {
updateErr := onRpcRuleUpdate(resRpcRuleMap)
if updateErr != nil {
logging.Error(updateErr, "[Fallback] update rpc rule failed")
err = updateErr
} else {
updated = true
}
} else {
logging.Info("[Fallback] Rpc load rules is the same with current rules, so ignore load operation.")
}
return updated, err
}

func onWebRuleUpdate(rawWebRuleMap map[string]map[FunctionType]*WebBlockFallbackBehavior) error {
start := util.CurrentTimeNano()
webRwMux.Lock()
webRuleMap = rawWebRuleMap
webRwMux.Unlock()
currentWebRules = rawWebRuleMap
logging.Debug("[Fallback onWebRuleUpdate] Time statistic(ns) for updating web fallback rule", "timeCost", util.CurrentTimeNano()-start)
return nil
}

func onRpcRuleUpdate(rawRpcRuleMap map[string]map[FunctionType]*RpcBlockFallbackBehavior) error {
start := util.CurrentTimeNano()
rpcRwMux.Lock()
rpcRuleMap = rawRpcRuleMap
rpcRwMux.Unlock()
currentRpcRules = rawRpcRuleMap
logging.Debug("[Fallback onRpcRuleUpdate] Time statistic(ns) for updating rpc fallback rule", "timeCost", util.CurrentTimeNano()-start)
return nil
}
67 changes: 67 additions & 0 deletions core/fallback/rule_manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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 fallback

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestLoadRule(t *testing.T) {
_, err := LoadRules([]*Rule{
{
TargetResourceType: WebResourceType,
TargetMap: map[string][]FunctionType{
"/greet": {
FlowType,
Isolation,
},
},
FallbackBehavior: []byte("{\"webFallbackMode\":0,\"webRespContentType\":1,\"webRespMessage\":\"{\\n \\\"abc\\\": 123\\n}\",\"webRespStatusCode\":433}"),
},
{
TargetResourceType: WebResourceType,
TargetMap: map[string][]FunctionType{
"/greet": {
HotspotHttp,
},
},
FallbackBehavior: []byte("{\"webFallbackMode\":0,\"webRespContentType\":1,\"webRespMessage\":\"{\\n \\\"abc\\\": 123\\n}\",\"webRespStatusCode\":434}"),
},
{
TargetResourceType: WebResourceType,
TargetMap: map[string][]FunctionType{
"/api/users/:id": {
FlowType,
},
},
FallbackBehavior: []byte("{\"webFallbackMode\":0,\"webRespContentType\":1,\"webRespMessage\":\"{\\n \\\"abc\\\": 123\\n}\",\"webRespStatusCode\":400}"),
},
})
assert.NoError(t, err)

assert.Equal(t, 2, len(webRuleMap))
assert.Equal(t, 0, len(rpcRuleMap))

funcTypeMap := webRuleMap["/greet"]
assert.Equal(t, 3, len(funcTypeMap))

assert.Equal(t, funcTypeMap[FlowType].WebRespStatusCode, int64(433))
assert.Equal(t, funcTypeMap[HotspotHttp].WebRespStatusCode, int64(434))

funcTypeMap = webRuleMap["/api/users/:id"]
assert.Equal(t, 1, len(funcTypeMap))

}
70 changes: 70 additions & 0 deletions core/fallback/slot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// 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 fallback

import (
"github.com/alibaba/sentinel-golang/core/base"
)

var blockType2FuncTypeMap = map[base.BlockType]FunctionType{
base.BlockTypeFlow: FlowType,
base.BlockTypeIsolation: Isolation,
base.BlockTypeHotSpotParamFlow: HotspotRpc,
}

func getFuncTypeByBlockType(blockType base.BlockType) (FunctionType, bool) {
funcType, ok := blockType2FuncTypeMap[blockType]
return funcType, ok
}

func GetWebFallbackBehavior(resource string, blockType base.BlockType) (*WebBlockFallbackBehavior, bool) {
functionType, ok := getFuncTypeByBlockType(blockType)
if !ok {
return nil, false
}

webRwMux.RLock()
defer webRwMux.RUnlock()

funcMap, ok := webRuleMap[resource]
if !ok {
return nil, false
}
behavior, ok := funcMap[functionType]
if !ok {
return nil, false
}
return behavior, true
}

func GetRpcFallbackBehavior(resource string, blockType base.BlockType) (*RpcBlockFallbackBehavior, bool) {
functionType, ok := getFuncTypeByBlockType(blockType)
if !ok {
return nil, false
}

rpcRwMux.RLock()
defer rpcRwMux.RUnlock()

funcMap, ok := rpcRuleMap[resource]
if !ok {
return nil, false
}
behavior, ok := funcMap[functionType]
if !ok {
return nil, false
}
return behavior, true
}
Loading

0 comments on commit 62502e7

Please sign in to comment.