From 783ba153bb9a5a9b027da39aac19a2bb10716fac Mon Sep 17 00:00:00 2001 From: liaochuntao Date: Thu, 24 Nov 2022 12:14:03 +0800 Subject: [PATCH] Feat/router label (#115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加就近路由支持文档 * rebase upstream/master * feat:support new route label * feat:support new route label * feat:support new route label * feat:support new route label * feat:support new route label --- .gitignore | 5 +- api.go | 15 +++++ api/consumer.go | 38 ++++++++++++ api/consumer_impl.go | 2 + api/consumer_test.go | 77 +++++++++++++++++++++++++ api_router.go | 1 + examples/route/dynamic/consumer/main.go | 13 +++-- import-format.sh | 41 +++++++++++++ pkg/model/argument.go | 33 ++++++++++- pkg/model/router.go | 10 ++++ pkg/model/service.go | 25 ++++++++ plugin/servicerouter/rulebase/base.go | 26 +++++++-- 12 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 api/consumer_test.go create mode 100644 import-format.sh diff --git a/.gitignore b/.gitignore index b0e3f7e8..a5a262bc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ test/other/other_test_suit.go /vendor/ -.vscode/ \ No newline at end of file +.vscode/ + +style_tool/ +goimports-reviser \ No newline at end of file diff --git a/api.go b/api.go index 6e965e03..d8d5c478 100644 --- a/api.go +++ b/api.go @@ -146,6 +146,21 @@ type ProcessRoutersRequest struct { model.ProcessRoutersRequest } +func (r *ProcessRoutersRequest) convert() { + if len(r.Arguments) == 0 { + return + } + + if len(r.SourceService.Metadata) == 0 { + r.SourceService.Metadata = map[string]string{} + } + + for i := range r.Arguments { + arg := r.Arguments[i] + arg.ToLabels(r.SourceService.Metadata) + } +} + // ProcessLoadBalanceRequest process load balancer to get the target instances type ProcessLoadBalanceRequest struct { model.ProcessLoadBalanceRequest diff --git a/api/consumer.go b/api/consumer.go index bb94713e..1427b532 100644 --- a/api/consumer.go +++ b/api/consumer.go @@ -47,11 +47,49 @@ type GetOneInstanceRequest struct { model.GetOneInstanceRequest } +func (r *GetOneInstanceRequest) convert() { + if len(r.Arguments) == 0 { + return + } + + serviceInfo := r.SourceService + if serviceInfo == nil { + r.SourceService = &model.ServiceInfo{ + Metadata: map[string]string{}, + } + serviceInfo = r.SourceService + } + + for i := range r.Arguments { + arg := r.Arguments[i] + arg.ToLabels(serviceInfo.Metadata) + } +} + // GetInstancesRequest 获取多个服务的请求对象 type GetInstancesRequest struct { model.GetInstancesRequest } +func (r *GetInstancesRequest) convert() { + if len(r.Arguments) == 0 { + return + } + + serviceInfo := r.SourceService + if serviceInfo == nil { + r.SourceService = &model.ServiceInfo{ + Metadata: map[string]string{}, + } + serviceInfo = r.SourceService + } + + for i := range r.Arguments { + arg := r.Arguments[i] + arg.ToLabels(serviceInfo.Metadata) + } +} + // GetAllInstancesRequest 获取服务下所有实例的请求对象 type GetAllInstancesRequest struct { model.GetAllInstancesRequest diff --git a/api/consumer_impl.go b/api/consumer_impl.go index 6d655702..c1f30533 100644 --- a/api/consumer_impl.go +++ b/api/consumer_impl.go @@ -43,6 +43,7 @@ func (c *consumerAPI) GetOneInstance(req *GetOneInstanceRequest) (*model.OneInst if err := req.Validate(); err != nil { return nil, err } + req.convert() return c.context.GetEngine().SyncGetOneInstance(&req.GetOneInstanceRequest) } @@ -54,6 +55,7 @@ func (c *consumerAPI) GetInstances(req *GetInstancesRequest) (*model.InstancesRe if err := req.Validate(); err != nil { return nil, err } + req.convert() return c.context.GetEngine().SyncGetInstances(&req.GetInstancesRequest) } diff --git a/api/consumer_test.go b/api/consumer_test.go new file mode 100644 index 00000000..5c1b3459 --- /dev/null +++ b/api/consumer_test.go @@ -0,0 +1,77 @@ +/** + * Tencent is pleased to support the open source community by making polaris-go available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 api + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/polarismesh/polaris-go/pkg/model" +) + +func TestGetInstancesRequest_convert(t *testing.T) { + type fields struct { + GetInstancesRequest model.GetInstancesRequest + } + tests := []struct { + name string + fields fields + ret map[string]string + }{ + { + fields: fields{ + GetInstancesRequest: model.GetInstancesRequest{ + SourceService: &model.ServiceInfo{ + Metadata: map[string]string{ + "uid": "123", + }, + }, + Arguments: []model.Argument{ + model.BuildHeaderArgument("uid", "123"), + }, + }, + }, + ret: map[string]string{ + "uid": "123", + "$header.uid": "123", + }, + }, + { + fields: fields{ + GetInstancesRequest: model.GetInstancesRequest{ + Arguments: []model.Argument{ + model.BuildHeaderArgument("uid", "123"), + }, + }, + }, + ret: map[string]string{ + "$header.uid": "123", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &GetInstancesRequest{ + GetInstancesRequest: tt.fields.GetInstancesRequest, + } + r.convert() + assert.Equal(t, tt.ret, r.SourceService.Metadata) + }) + } +} diff --git a/api_router.go b/api_router.go index 33162c84..d14756a8 100644 --- a/api_router.go +++ b/api_router.go @@ -35,6 +35,7 @@ func (r *routerAPI) ProcessRouters(request *ProcessRoutersRequest) (*model.Insta if err := request.Validate(); err != nil { return nil, err } + request.convert() return r.sdkCtx.GetEngine().ProcessRouters(&request.ProcessRoutersRequest) } diff --git a/examples/route/dynamic/consumer/main.go b/examples/route/dynamic/consumer/main.go index 20282dbe..09f2adaf 100644 --- a/examples/route/dynamic/consumer/main.go +++ b/examples/route/dynamic/consumer/main.go @@ -30,6 +30,7 @@ import ( "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/config" + "github.com/polarismesh/polaris-go/pkg/model" ) var ( @@ -104,7 +105,7 @@ func (svr *PolarisConsumer) runWebServer() { routerRequest.DstInstances = instancesResp routerRequest.SourceService.Service = selfService routerRequest.SourceService.Namespace = selfNamespace - routerRequest.SourceService.Metadata = convertQuery(r.URL.RawQuery) + routerRequest.AddArguments(convertQuery(r.URL.RawQuery)...) routerInstancesResp, err := svr.router.ProcessRouters(routerRequest) if nil != err { log.Printf("[error] fail to processRouters, err is %v", err) @@ -190,19 +191,19 @@ func main() { } -func convertQuery(rawQuery string) map[string]string { - meta := make(map[string]string) +func convertQuery(rawQuery string) []model.Argument { + arguments := make([]model.Argument, 0, 4) if len(rawQuery) == 0 { - return meta + return arguments } tokens := strings.Split(rawQuery, "&") if len(tokens) > 0 { for _, token := range tokens { values := strings.Split(token, "=") - meta[values[0]] = values[1] + arguments = append(arguments, model.BuildQueryArgument(values[0], values[1])) } } - return meta + return arguments } func getLocalHost(serverAddr string) (string, error) { diff --git a/import-format.sh b/import-format.sh new file mode 100644 index 00000000..976ee2b6 --- /dev/null +++ b/import-format.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Tencent is pleased to support the open source community by making Polaris available. +# +# Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +# +# Licensed under the BSD 3-Clause License (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://opensource.org/licenses/BSD-3-Clause +# +# 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. + + +# 格式化 go.mod +go mod tidy -compat=1.17 + + +# 处理 go imports 的格式化 +rm -rf style_tool +rm -rf goimports-reviser + +mkdir -p style_tool + +cd style_tool + +wget https://github.com/incu6us/goimports-reviser/releases/download/v3.1.1/goimports-reviser_3.1.1_linux_amd64.tar.gz +tar -zxvf goimports-reviser_3.1.1_linux_amd64.tar.gz +mv goimports-reviser ../ + +cd ../ + +ls -lstrh + +find . -name "*.go" -type f | grep -v .pb.go|grep -v test/tools/tools.go | grep -v ./pkg/plugin/register/plugins.go | xargs -I {} goimports-reviser -rm-unused -format {} -project-name github.com/polarismesh/polaris-go + +# 处理 go 代码格式化 +go fmt ./... \ No newline at end of file diff --git a/pkg/model/argument.go b/pkg/model/argument.go index da1ff0a8..3130befa 100644 --- a/pkg/model/argument.go +++ b/pkg/model/argument.go @@ -29,6 +29,8 @@ const ( ArgumentTypeQuery ArgumentTypeCallerService ArgumentTypeCallerIP + ArgumentTypePath + ArgumentTypeCookie ) var argumentTypeToName = map[int]string{ @@ -38,6 +40,8 @@ var argumentTypeToName = map[int]string{ ArgumentTypeQuery: "QUERY", ArgumentTypeCallerService: "CALLER_SERVICE", ArgumentTypeCallerIP: "CALLER_IP", + ArgumentTypePath: "PATH", + ArgumentTypeCookie: "COOKIE", } const ( @@ -46,9 +50,11 @@ const ( LabelKeyQuery = "$query." LabelKeyCallerService = "$caller_service." LabelKeyCallerIp = "$caller_ip" + LabelKeyPath = "$path" + LabelKeyCookie = "$cookie." ) -// Argument 限流参数 +// Argument 限流/路由参数 type Argument struct { argumentType int @@ -119,6 +125,21 @@ func BuildCallerIPArgument(callerIP string) Argument { } } +func BuildPathArgument(path string) Argument { + return Argument{ + argumentType: ArgumentTypePath, + value: path, + } +} + +func BuildCookieArgument(key, value string) Argument { + return Argument{ + argumentType: ArgumentTypeCookie, + key: key, + value: value, + } +} + func BuildArgumentFromLabel(labelKey string, labelValue string) Argument { if labelKey == LabelKeyMethod { return BuildMethodArgument(labelValue) @@ -126,6 +147,9 @@ func BuildArgumentFromLabel(labelKey string, labelValue string) Argument { if labelKey == LabelKeyCallerIp { return BuildCallerIPArgument(labelValue) } + if labelKey == LabelKeyPath { + return BuildPathArgument(labelValue) + } if strings.HasPrefix(labelKey, LabelKeyHeader) { return BuildHeaderArgument(labelKey[len(LabelKeyHeader):], labelValue) } @@ -135,6 +159,9 @@ func BuildArgumentFromLabel(labelKey string, labelValue string) Argument { if strings.HasPrefix(labelKey, LabelKeyCallerService) { return BuildCallerServiceArgument(labelKey[len(LabelKeyCallerService):], labelValue) } + if strings.HasPrefix(labelKey, LabelKeyCookie) { + return BuildCookieArgument(labelKey[len(LabelKeyCookie):], labelValue) + } return BuildCustomArgument(labelKey, labelValue) } @@ -152,5 +179,9 @@ func (a Argument) ToLabels(labels map[string]string) { labels[LabelKeyCallerService+a.key] = a.value case ArgumentTypeCustom: labels[a.key] = a.value + case ArgumentTypePath: + labels[LabelKeyPath] = a.value + case ArgumentTypeCookie: + labels[LabelKeyCookie+a.key] = a.value } } diff --git a/pkg/model/router.go b/pkg/model/router.go index 7da7b802..b5d7f38d 100644 --- a/pkg/model/router.go +++ b/pkg/model/router.go @@ -26,6 +26,8 @@ type ProcessRoutersRequest struct { Routers []string // SourceService indicate the source service to match the route rule, optional. SourceService ServiceInfo + // Arguments traffic labels + Arguments []Argument // DstInstances indicate the destination instances resolved from discovery, required. // Two implementations to ServiceInstances: // 1. InstancesResponse, returned from ConsumerAPI.GetAllInstances. @@ -47,6 +49,14 @@ func (p *ProcessRoutersRequest) GetResponse() *InstancesResponse { return &p.response } +// AddArgument add one traffic label +func (p *ProcessRoutersRequest) AddArguments(arg ...Argument) { + if len(p.Arguments) == 0 { + p.Arguments = make([]Argument, 0, 4) + } + p.Arguments = append(p.Arguments, arg...) +} + // Validate validate the request object func (p *ProcessRoutersRequest) Validate() error { if nil == p { diff --git a/pkg/model/service.go b/pkg/model/service.go index 60226154..f3378f2c 100644 --- a/pkg/model/service.go +++ b/pkg/model/service.go @@ -247,6 +247,8 @@ type GetOneInstanceRequest struct { HashValue uint64 // 主调方服务信息 SourceService *ServiceInfo + // 路由标签参数 + Arguments []Argument // 可选,单次查询超时时间,默认直接获取全局的超时配置 // 用户总最大超时时间为(1+RetryCount) * Timeout Timeout *time.Duration @@ -313,6 +315,14 @@ func (g *GetOneInstanceRequest) SetCanary(canary string) { g.Canary = canary } +// AddArguments . +func (g *GetOneInstanceRequest) AddArguments(argumet ...Argument) { + if len(g.Arguments) == 0 { + g.Arguments = make([]Argument, 0, 4) + } + g.Arguments = append(g.Arguments, argumet...) +} + // Validate 校验获取单个服务实例请求对象 func (g *GetOneInstanceRequest) Validate() error { if nil == g { @@ -406,6 +416,8 @@ type GetInstancesRequest struct { Metadata map[string]string // 主调方服务信息,只用于路由规则匹配 SourceService *ServiceInfo + // 路由标签参数 + Arguments []Argument // 可选,是否包含被熔断的服务实例,默认false // Deprecated: 已弃用,1.0版本后会正式去掉,需要返回全量IP直接设置SkipRouteFilter=true IncludeCircuitBreakInstances bool @@ -475,6 +487,14 @@ func (g *GetInstancesRequest) SetCanary(canary string) { g.Canary = canary } +// AddArguments . +func (g *GetInstancesRequest) AddArguments(argumet ...Argument) { + if len(g.Arguments) == 0 { + g.Arguments = make([]Argument, 0, 4) + } + g.Arguments = append(g.Arguments, argumet...) +} + // Validate 校验获取全部服务实例请求对象 func (g *GetInstancesRequest) Validate() error { if nil == g { @@ -602,6 +622,11 @@ type ServiceInfo struct { Metadata map[string]string } +// AddArgument 添加本次流量标签参数 +func (i *ServiceInfo) AddArgument(arg Argument) { + arg.ToLabels(i.Metadata) +} + // GetService 获取服务名 func (i *ServiceInfo) GetService() string { return i.Service diff --git a/plugin/servicerouter/rulebase/base.go b/plugin/servicerouter/rulebase/base.go index 8e38248b..70ab767a 100644 --- a/plugin/servicerouter/rulebase/base.go +++ b/plugin/servicerouter/rulebase/base.go @@ -20,6 +20,7 @@ package rulebase import ( "os" "sort" + "strings" regexp "github.com/dlclark/regexp2" "github.com/modern-go/reflect2" @@ -174,10 +175,27 @@ func (g *RuleBasedInstancesFilter) matchSourceMetadata(ruleMeta map[string]*nami if m == nil || m.String() == "" { allMetaMatched = false } - default: - // 精确匹配 - if srcMetaValue != rawMetaValue { - allMetaMatched = false + case namingpb.MatchString_NOT_EQUALS: + allMetaMatched = srcMetaValue != rawMetaValue + case namingpb.MatchString_EXACT: + allMetaMatched = srcMetaValue == rawMetaValue + case namingpb.MatchString_IN: + find := false + tokens := strings.Split(rawMetaValue, ",") + for _, token := range tokens { + if token == srcMetaValue { + find = true + break + } + } + allMetaMatched = find + case namingpb.MatchString_NOT_IN: + tokens := strings.Split(rawMetaValue, ",") + for _, token := range tokens { + if token == srcMetaValue { + allMetaMatched = false + break + } } } } else {