[frontend-gray] Increase gray types according to the ratio-weight gray (#1291)

This commit is contained in:
mamba
2024-09-22 16:49:54 +08:00
committed by GitHub
parent ffc5458a91
commit ee67553816
6 changed files with 387 additions and 118 deletions

View File

@@ -1,11 +1,14 @@
package util
import (
"fmt"
"math/rand"
"net/url"
"path"
"path/filepath"
"sort"
"strings"
"time"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
@@ -14,22 +17,53 @@ import (
"github.com/tidwall/gjson"
)
func LogInfof(format string, args ...interface{}) {
format = fmt.Sprintf("[%s] %s", "frontend-gray", format)
proxywasm.LogInfof(format, args...)
}
func GetXPreHigressVersion(cookies string) (string, string) {
xPreHigressVersion := ExtractCookieValueByKey(cookies, config.XPreHigressTag)
preVersions := strings.Split(xPreHigressVersion, ",")
if len(preVersions) == 0 {
return "", ""
}
if len(preVersions) == 1 {
return preVersions[0], ""
}
return strings.TrimSpace(preVersions[0]), strings.TrimSpace(preVersions[1])
}
// 从xff中获取真实的IP
func GetRealIpFromXff(xff string) string {
if xff != "" {
// 通常客户端的真实 IP 是 XFF 头中的第一个 IP
ips := strings.Split(xff, ",")
if len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
return ""
}
func IsGrayEnabled(grayConfig config.GrayConfig) bool {
// 检查是否存在重写主机
if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" {
return true
}
// 检查灰度部署是否为 nil 或空
grayDeployments := grayConfig.GrayDeployments
if grayDeployments != nil && len(grayDeployments) > 0 {
for _, grayDeployment := range grayDeployments {
if grayDeployment.Enabled {
return true
}
// 检查是否存在灰度版本配置
return len(grayConfig.GrayDeployments) > 0
}
// 是否启用后端的灰度(全链路灰度)
func IsBackendGrayEnabled(grayConfig config.GrayConfig) bool {
for _, deployment := range grayConfig.GrayDeployments {
if deployment.BackendVersion != "" {
return true
}
}
return false
}
@@ -98,12 +132,11 @@ var indexSuffixes = []string{
".html", ".htm", ".jsp", ".php", ".asp", ".aspx", ".erb", ".ejs", ".twig",
}
// IsIndexRequest determines if the request is an index request
func IsIndexRequest(fetchMode string, p string) bool {
func IsPageRequest(fetchMode string, myPath string) bool {
if fetchMode == "cors" {
return false
}
ext := path.Ext(p)
ext := path.Ext(myPath)
return ext == "" || ContainsValue(indexSuffixes, ext)
}
@@ -133,22 +166,25 @@ func PrefixFileRewrite(path, version string, matchRules map[string]string) strin
return filepath.Clean(newPath)
}
func GetVersion(version string, cookies string, isIndex bool) string {
if isIndex {
return version
func GetVersion(grayConfig config.GrayConfig, deployment *config.Deployment, xPreHigressVersion string, isPageRequest bool) *config.Deployment {
if isPageRequest {
return deployment
}
// 来自Cookie中的版本
cookieVersion := ExtractCookieValueByKey(cookies, config.XPreHigressTag)
// cookie 中为空,返回当前版本
if cookieVersion == "" {
return version
if xPreHigressVersion == "" {
return deployment
}
// cookie 中和当前版本不相同返回cookie中值
if cookieVersion != version {
return cookieVersion
if xPreHigressVersion != deployment.Version {
deployments := append(grayConfig.GrayDeployments, grayConfig.BaseDeployment)
for _, curDeployment := range deployments {
if curDeployment.Version == xPreHigressVersion {
return curDeployment
}
}
}
return version
return grayConfig.BaseDeployment
}
// 从cookie中解析出灰度信息
@@ -169,7 +205,12 @@ func getBySubKey(grayInfoStr string, graySubKey string) string {
return value.String()
}
func GetGrayKey(grayKeyValue string, graySubKey string) string {
func GetGrayKey(grayKeyValueByCookie string, grayKeyValueByHeader string, graySubKey string) string {
grayKeyValue := grayKeyValueByCookie
if grayKeyValueByCookie == "" {
grayKeyValue = grayKeyValueByHeader
}
// 如果有子key, 尝试从子key中获取值
if graySubKey != "" {
subKeyValue := getBySubKey(grayKeyValue, graySubKey)
@@ -181,18 +222,13 @@ func GetGrayKey(grayKeyValue string, graySubKey string) string {
}
// FilterGrayRule 过滤灰度规则
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, logInfof func(format string, args ...interface{})) *config.GrayDeployment {
for _, grayDeployment := range grayConfig.GrayDeployments {
if !grayDeployment.Enabled {
// 跳过Enabled=false
continue
}
grayRule := GetRule(grayConfig.Rules, grayDeployment.Name)
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment {
for _, deployment := range grayConfig.GrayDeployments {
grayRule := GetRule(grayConfig.Rules, deployment.Name)
// 首先先校验用户名单ID
if grayRule.GrayKeyValue != nil && len(grayRule.GrayKeyValue) > 0 && grayKeyValue != "" {
if ContainsValue(grayRule.GrayKeyValue, grayKeyValue) {
logInfof("frontendVersion: %s, grayKeyValue: %s", grayDeployment.Version, grayKeyValue)
return grayDeployment
return deployment
}
}
// 第二校验Cookie中的 GrayTagKey
@@ -200,11 +236,45 @@ func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, logInfof
cookieStr, _ := proxywasm.GetHttpRequestHeader("cookie")
grayTagValue := ExtractCookieValueByKey(cookieStr, grayRule.GrayTagKey)
if ContainsValue(grayRule.GrayTagValue, grayTagValue) {
logInfof("frontendVersion: %s, grayTag: %s=%s", grayDeployment.Version, grayRule.GrayTagKey, grayTagValue)
return grayDeployment
return deployment
}
}
}
logInfof("frontendVersion: %s, grayKeyValue: %s", grayConfig.BaseDeployment.Version, grayKeyValue)
return grayConfig.BaseDeployment
}
func FilterGrayWeight(grayConfig *config.GrayConfig, preVersion string, preUniqueClientId string, uniqueClientId string) *config.Deployment {
// 如果没有灰度权重,直接返回基础版本
if grayConfig.TotalGrayWeight == 0 {
return grayConfig.BaseDeployment
}
deployments := append(grayConfig.GrayDeployments, grayConfig.BaseDeployment)
LogInfof("preVersion: %s, preUniqueClientId: %s, uniqueClientId: %s", preVersion, preUniqueClientId, uniqueClientId)
// 用户粘滞,确保每个用户每次访问的都是走同一版本
if preVersion != "" && uniqueClientId == preUniqueClientId {
for _, deployment := range deployments {
if deployment.Version == preVersion {
return deployment
}
}
}
totalWeight := 100
// 如果总权重小于100则将基础版本也加入到总版本列表中
if grayConfig.TotalGrayWeight <= totalWeight {
grayConfig.BaseDeployment.Weight = 100 - grayConfig.TotalGrayWeight
} else {
totalWeight = grayConfig.TotalGrayWeight
}
rand.Seed(time.Now().UnixNano())
randWeight := rand.Intn(totalWeight)
sumWeight := 0
for _, deployment := range deployments {
sumWeight += deployment.Weight
if randWeight < sumWeight {
return deployment
}
}
return nil
}

View File

@@ -3,7 +3,9 @@ package util
import (
"testing"
"github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
)
func TestExtractCookieValueByKey(t *testing.T) {
@@ -80,7 +82,7 @@ func TestPrefixFileRewrite(t *testing.T) {
}
}
func TestIsIndexRequest(t *testing.T) {
func TestIsPageRequest(t *testing.T) {
var tests = []struct {
fetchMode string
p string
@@ -97,8 +99,26 @@ func TestIsIndexRequest(t *testing.T) {
for _, test := range tests {
testPath := test.p
t.Run(testPath, func(t *testing.T) {
output := IsIndexRequest(test.fetchMode, testPath)
output := IsPageRequest(test.fetchMode, testPath)
assert.Equal(t, test.output, output)
})
}
}
func TestFilterGrayWeight(t *testing.T) {
var tests = []struct {
name string
input string
}{
{"demo", `{"grayKey":"userId","rules":[{"name":"inner-user","grayKeyValue":["00000001","00000005"]},{"name":"beta-user","grayKeyValue":["noah","00000003"],"grayTagKey":"level","grayTagValue":["level3","level5"]}],"rewrite":{"host":"frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com","notFoundUri":"/mfe/app1/dev/404.html","indexRouting":{"/app1":"/mfe/app1/{version}/index.html","/":"/mfe/app1/{version}/index.html"},"fileRouting":{"/":"/mfe/app1/{version}","/app1":"/mfe/app1/{version}"}},"baseDeployment":{"version":"dev"},"grayDeployments":[{"name":"beta-user","version":"0.0.1","backendVersion":"beta","enabled":true,"weight":50}]}`},
}
for _, test := range tests {
testName := test.name
t.Run(testName, func(t *testing.T) {
grayConfig := &config.GrayConfig{}
config.JsonToGrayConfig(gjson.Parse(test.input), grayConfig)
result := FilterGrayWeight(grayConfig, "base", "1.0.1", "192.168.1.1")
t.Logf("result-----: %v", result)
})
}
}