mirror of
https://github.com/alibaba/higress.git
synced 2026-04-21 20:17:29 +08:00
[frontend-gray] Increase gray types according to the ratio-weight gray (#1291)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user