| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 condition |
| |
| import ( |
| "sync" |
| "testing" |
| ) |
| |
| import ( |
| "github.com/stretchr/testify/assert" |
| ) |
| |
| import ( |
| "dubbo.apache.org/dubbo-go/v3/common" |
| commonConfig "dubbo.apache.org/dubbo-go/v3/common/config" |
| "dubbo.apache.org/dubbo-go/v3/common/constant" |
| "dubbo.apache.org/dubbo-go/v3/common/extension" |
| "dubbo.apache.org/dubbo-go/v3/config" |
| "dubbo.apache.org/dubbo-go/v3/config_center" |
| "dubbo.apache.org/dubbo-go/v3/config_center/configurator" |
| "dubbo.apache.org/dubbo-go/v3/protocol/base" |
| "dubbo.apache.org/dubbo-go/v3/protocol/invocation" |
| "dubbo.apache.org/dubbo-go/v3/remoting" |
| ) |
| |
| const ( |
| conditionAddr = "condition://127.0.0.1/com.foo.BarService" |
| |
| localConsumerAddr = "consumer://127.0.0.1/com.foo.BarService" |
| remoteConsumerAddr = "consumer://dubbo.apache.org/com.foo.BarService" |
| |
| localProviderAddr = "dubbo://127.0.0.1:20880/com.foo.BarService" |
| remoteProviderAddr = "dubbo://dubbo.apache.org:20880/com.foo.BarService" |
| emptyProviderAddr = "" |
| |
| region = "?region=hangzhou" |
| method = "getFoo" |
| ) |
| |
| func TestRouteMatchWhen(t *testing.T) { |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| whenConsumerURL, _ := common.NewURL(remoteConsumerAddr) |
| |
| testData := []struct { |
| name string |
| consumerUrl *common.URL |
| rule string |
| |
| wantVal bool |
| }{ |
| { |
| name: " => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: " => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "host = 1.2.3.4 => ", |
| consumerUrl: whenConsumerURL, |
| rule: "host = 1.2.3.4 => ", |
| |
| wantVal: false, |
| }, |
| { |
| name: "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 & host !=dubbo.apache.org => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host = 2.2.2.2,dubbo.apache.org,3.3.3.3 & host !=dubbo.apache.org => host = 1.2.3.4", |
| |
| wantVal: false, |
| }, |
| { |
| name: "host !=4.4.4.4 & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host !=4.4.4.4 & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "host !=4.4.4.* & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host !=4.4.4.* & host = 2.2.2.2,dubbo.apache.org,3.3.3.3 => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != dubbo.apache.org => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != dubbo.apache.org => host = 1.2.3.4", |
| |
| wantVal: false, |
| }, |
| { |
| name: "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4", |
| consumerUrl: whenConsumerURL, |
| rule: "host = 2.2.2.2,dubbo.apache.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| resVal := router.matchWhen(data.consumerUrl, rpcInvocation) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| // TestRouteMatchFilter also tests pattern_value.WildcardValuePattern's Match method |
| func TestRouteMatchFilter(t *testing.T) { |
| |
| consumerURL, _ := common.NewURL(localConsumerAddr) |
| url1, _ := common.NewURL(remoteProviderAddr + "?serialization=fastjson") |
| url2, _ := common.NewURL(localProviderAddr) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| testData := []struct { |
| name string |
| comsumerURL *common.URL |
| rule string |
| |
| wantVal int |
| }{ |
| { |
| name: "host = 127.0.0.1 => host = dubbo.apache.org", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => host = dubbo.apache.org", |
| |
| wantVal: 1, |
| }, |
| { |
| name: "host = 127.0.0.1 => host = 10.20.3.* & host != dubbo.apache.org", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => host = 10.20.3.* & host != dubbo.apache.org", |
| |
| wantVal: 0, |
| }, |
| { |
| name: "host = 127.0.0.1 => host = dubbo.apache.org & host != dubbo.apache.org", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => host = dubbo.apache.org & host != dubbo.apache.org", |
| |
| wantVal: 0, |
| }, |
| { |
| name: "host = 127.0.0.1 => host = 10.20.3.2,dubbo.apache.org,10.20.3.4", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => host = 10.20.3.2,dubbo.apache.org,10.20.3.4", |
| |
| wantVal: 1, |
| }, |
| { |
| name: "host = 127.0.0.1 => host != dubbo.apache.org", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => host != dubbo.apache.org", |
| |
| wantVal: 2, |
| }, |
| { |
| name: "host = 127.0.0.1 => serialization = fastjson", |
| comsumerURL: consumerURL, |
| rule: "host = 127.0.0.1 => serialization = fastjson", |
| |
| wantVal: 1, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| url.AddParam(constant.ForceKey, "true") |
| |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| filteredInvokers := router.Route(invokerList, data.comsumerURL, rpcInvocation) |
| resVal := len(filteredInvokers) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| func TestRouterMethodRoute(t *testing.T) { |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| |
| testData := []struct { |
| name string |
| consumerURL string |
| rule string |
| |
| wantVal bool |
| }{ |
| { |
| name: "More than one methods, mismatch", |
| consumerURL: remoteConsumerAddr + "?methods=setFoo,getFoo,findFoo", |
| rule: "methods=getFoo => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "Exactly one method, Route", |
| consumerURL: remoteConsumerAddr + "?methods=getFoo", |
| rule: "methods=getFoo => host = 1.2.3.4", |
| |
| wantVal: true, |
| }, |
| { |
| name: "Method routing and Other condition routing can work together", |
| consumerURL: remoteConsumerAddr + "?methods=getFoo", |
| rule: "methods=getFoo & host!=dubbo.apache.org => host = 1.2.3.4", |
| |
| wantVal: false, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| consumer, _ := common.NewURL(data.consumerURL) |
| resVal := router.matchWhen(consumer, rpcInvocation) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| func TestRouteReturn(t *testing.T) { |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| consumerURL, _ := common.NewURL(localConsumerAddr) |
| |
| testData := []struct { |
| name string |
| urls []string |
| rule string |
| |
| wantUrls []string |
| wantVal int |
| }{ |
| { |
| name: "ReturnFalse", |
| urls: []string{ |
| emptyProviderAddr, |
| emptyProviderAddr, |
| emptyProviderAddr, |
| }, |
| rule: "host = 127.0.0.1 => false", |
| |
| wantUrls: []string{}, |
| wantVal: 0, |
| }, |
| { |
| name: "ReturnEmpty", |
| urls: []string{ |
| emptyProviderAddr, |
| emptyProviderAddr, |
| emptyProviderAddr, |
| }, |
| rule: "host = 127.0.0.1 => ", |
| |
| wantUrls: []string{}, |
| wantVal: 0, |
| }, |
| { |
| name: "ReturnAll", |
| urls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| rule: "host = 127.0.0.1 => host = 127.0.0.1", |
| |
| wantUrls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| wantVal: 3, |
| }, |
| { |
| name: "HostFilter", |
| urls: []string{ |
| remoteProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| rule: "host = 127.0.0.1 => host = 127.0.0.1", |
| |
| wantUrls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| wantVal: 2, |
| }, |
| { |
| name: "EmptyHostFilter", |
| urls: []string{ |
| remoteProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| rule: " => host = 127.0.0.1", |
| |
| wantUrls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| wantVal: 2, |
| }, |
| { |
| name: "FalseHostFilter", |
| urls: []string{ |
| remoteProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| rule: "true => host = 127.0.0.1", |
| |
| wantUrls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| wantVal: 2, |
| }, |
| { |
| name: "PlaceHolder", |
| urls: []string{ |
| remoteProviderAddr, |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| rule: "host = 127.0.0.1 => host = $host", |
| |
| wantUrls: []string{ |
| localProviderAddr, |
| localProviderAddr, |
| }, |
| wantVal: 2, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| |
| invokers := make([]base.Invoker, 0, len(data.urls)) |
| for _, urlStr := range data.urls { |
| url, _ := common.NewURL(urlStr) |
| invoker := base.NewBaseInvoker(url) |
| invokers = append(invokers, invoker) |
| } |
| |
| wantInvokers := make([]base.Invoker, 0, len(data.wantUrls)) |
| for _, wantUrlStr := range data.wantUrls { |
| url, _ := common.NewURL(wantUrlStr) |
| invoker := base.NewBaseInvoker(url) |
| wantInvokers = append(wantInvokers, invoker) |
| } |
| |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| filterInvokers := router.Route(invokers, consumerURL, rpcInvocation) |
| resVal := len(filterInvokers) |
| |
| assert.Equal(t, data.wantVal, resVal) |
| assert.Equal(t, wantInvokers, filterInvokers) |
| }) |
| } |
| } |
| |
| // TestRouteArguments also tests matcher.ArgumentConditionMatcher's GetValue method |
| func TestRouteArguments(t *testing.T) { |
| |
| url1, _ := common.NewURL(remoteProviderAddr) |
| url2, _ := common.NewURL(localProviderAddr) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| consumerURL, _ := common.NewURL(localConsumerAddr) |
| |
| testData := []struct { |
| name string |
| argument any |
| rule string |
| |
| wantVal int |
| }{ |
| { |
| name: "Empty arguments", |
| argument: nil, |
| rule: "arguments[0] = a => host = 1.2.3.4", |
| |
| wantVal: 3, |
| }, |
| { |
| name: "String arguments", |
| argument: "a", |
| rule: "arguments[0] = a => host = 1.2.3.4", |
| |
| wantVal: 0, |
| }, |
| { |
| name: "Int arguments", |
| argument: 1, |
| rule: "arguments[0] = 1 => host = 127.0.0.1", |
| |
| wantVal: 2, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| url.AddParam(constant.ForceKey, "true") |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| arguments := make([]any, 0, 1) |
| arguments = append(arguments, data.argument) |
| |
| rpcInvocation := invocation.NewRPCInvocation("getBar", arguments, nil) |
| |
| filterInvokers := router.Route(invokerList, consumerURL, rpcInvocation) |
| resVal := len(filterInvokers) |
| assert.Equal(t, data.wantVal, resVal) |
| |
| }) |
| } |
| } |
| |
| // TestRouteAttachments also tests matcher.AttachmentConditionMatcher's GetValue method |
| func TestRouteAttachments(t *testing.T) { |
| consumerURL, _ := common.NewURL(localConsumerAddr) |
| |
| url1, _ := common.NewURL(remoteProviderAddr + region) |
| url2, _ := common.NewURL(localProviderAddr) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| testData := []struct { |
| name string |
| attachmentKey string |
| attachmentValue string |
| rule string |
| |
| wantVal int |
| }{ |
| { |
| name: "Empty attachments", |
| attachmentKey: "", |
| attachmentValue: "", |
| rule: "attachments[foo] = a => host = 1.2.3.4", |
| |
| wantVal: 3, |
| }, |
| { |
| name: "Yes attachments and no host", |
| attachmentKey: "foo", |
| attachmentValue: "a", |
| rule: "attachments[foo] = a => host = 1.2.3.4", |
| |
| wantVal: 0, |
| }, |
| { |
| name: "No attachments and no host", |
| attachmentKey: "foo", |
| attachmentValue: "a", |
| rule: "attachments = a => host = 1.2.3.4", |
| |
| wantVal: 3, |
| }, |
| { |
| name: "Yes attachments and region", |
| attachmentKey: "foo", |
| attachmentValue: "a", |
| rule: "attachments[foo] = a => region = hangzhou", |
| |
| wantVal: 1, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| rpcInvocation.SetAttachment(data.attachmentKey, data.attachmentValue) |
| |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| url.AddParam(constant.ForceKey, "true") |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| filterInvokers := router.Route(invokerList, consumerURL, rpcInvocation) |
| |
| resVal := len(filterInvokers) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| // TestRouteRangePattern also tests pattern_value.ScopeValuePattern's Match method |
| func TestRouteRangePattern(t *testing.T) { |
| |
| consumerURL, _ := common.NewURL(localConsumerAddr) |
| |
| url1, _ := common.NewURL(remoteProviderAddr + region) |
| url2, _ := common.NewURL(localProviderAddr) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| testData := []struct { |
| name string |
| attachmentKey string |
| attachmentValue string |
| rule string |
| |
| wantVal int |
| }{ |
| { |
| name: "Empty attachment", |
| attachmentKey: "", |
| attachmentValue: "", |
| rule: "attachments[user_id] = 1~100 => region=hangzhou", |
| |
| wantVal: 3, |
| }, |
| { |
| name: "In the range", |
| attachmentKey: "user_id", |
| attachmentValue: "80", |
| rule: "attachments[user_id] = 1~100 => region=hangzhou", |
| |
| wantVal: 1, |
| }, |
| { |
| name: "Out of range", |
| attachmentKey: "user_id", |
| attachmentValue: "101", |
| rule: "attachments[user_id] = 1~100 => region=hangzhou", |
| |
| wantVal: 3, |
| }, |
| { |
| name: "In the single interval range", |
| attachmentKey: "user_id", |
| attachmentValue: "1", |
| rule: "attachments[user_id] = ~100 => region=hangzhou", |
| |
| wantVal: 1, |
| }, |
| { |
| name: "Not in the single interval range", |
| attachmentKey: "user_id", |
| attachmentValue: "101", |
| rule: "attachments[user_id] = ~100 => region=hangzhou", |
| |
| wantVal: 3, |
| }, |
| } |
| |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, nil, nil) |
| rpcInvocation.SetAttachment(data.attachmentKey, data.attachmentValue) |
| |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| url.AddParam(constant.ForceKey, "true") |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| filterInvokers := router.Route(invokerList, consumerURL, rpcInvocation) |
| |
| resVal := len(filterInvokers) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| func TestRouteMultipleConditions(t *testing.T) { |
| url1, _ := common.NewURL(remoteProviderAddr + region) |
| url2, _ := common.NewURL(localProviderAddr) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| testData := []struct { |
| name string |
| argument string |
| consumerURL string |
| rule string |
| |
| wantVal int |
| }{ |
| { |
| name: "All conditions Route", |
| argument: "a", |
| consumerURL: localConsumerAddr + "?application=consumer_app", |
| rule: "application=consumer_app&arguments[0]=a => host = 127.0.0.1", |
| |
| wantVal: 2, |
| }, |
| { |
| name: "One of the conditions does not Route", |
| argument: "a", |
| consumerURL: localConsumerAddr + "?application=another_consumer_app", |
| rule: "application=consumer_app&arguments[0]=a => host = 127.0.0.1", |
| |
| wantVal: 3, |
| }, |
| } |
| for _, data := range testData { |
| t.Run(data.name, func(t *testing.T) { |
| consumerUrl, err := common.NewURL(data.consumerURL) |
| assert.Nil(t, err) |
| |
| url, err := common.NewURL(conditionAddr) |
| assert.Nil(t, err) |
| url.AddParam(constant.RuleKey, data.rule) |
| url.AddParam(constant.ForceKey, "true") |
| router, err := NewConditionStateRouter(url) |
| assert.Nil(t, err) |
| |
| arguments := make([]any, 0, 1) |
| arguments = append(arguments, data.argument) |
| |
| rpcInvocation := invocation.NewRPCInvocation(method, arguments, nil) |
| |
| filterInvokers := router.Route(invokerList, consumerUrl, rpcInvocation) |
| resVal := len(filterInvokers) |
| assert.Equal(t, data.wantVal, resVal) |
| }) |
| } |
| } |
| |
| func TestServiceRouter(t *testing.T) { |
| |
| consumerURL, _ := common.NewURL(remoteConsumerAddr) |
| |
| url1, _ := common.NewURL(remoteProviderAddr) |
| url2, _ := common.NewURL(remoteProviderAddr + region) |
| url3, _ := common.NewURL(localProviderAddr) |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| extension.SetDefaultConfigurator(configurator.NewMockConfigurator) |
| ccURL, _ := common.NewURL("mock://127.0.0.1:1111") |
| mockFactory := &config_center.MockDynamicConfigurationFactory{ |
| Content: ` |
| configVersion: v3.0 |
| scope: service |
| force: true |
| enabled: true |
| runtime: true |
| key: com.foo.BarService |
| conditions: |
| - 'method=sayHello => region=hangzhou'`, |
| } |
| dc, _ := mockFactory.GetDynamicConfiguration(ccURL) |
| commonConfig.GetEnvInstance().SetDynamicConfiguration(dc) |
| |
| router := NewServiceRouter() |
| router.Notify(invokerList) |
| |
| rpcInvocation := invocation.NewRPCInvocation("sayHello", nil, nil) |
| invokers := router.Route(invokerList, consumerURL, rpcInvocation) |
| assert.Equal(t, 1, len(invokers)) |
| |
| rpcInvocation = invocation.NewRPCInvocation("sayHi", nil, nil) |
| invokers = router.Route(invokerList, consumerURL, rpcInvocation) |
| assert.Equal(t, 3, len(invokers)) |
| } |
| |
| func TestApplicationRouter(t *testing.T) { |
| |
| consumerURL, _ := common.NewURL(remoteConsumerAddr) |
| |
| url1, _ := common.NewURL(remoteProviderAddr + "?application=demo-provider") |
| url2, _ := common.NewURL(localProviderAddr + "?application=demo-provider®ion=hangzhou") |
| url3, _ := common.NewURL(localProviderAddr + "?application=demo-provider") |
| |
| ink1 := base.NewBaseInvoker(url1) |
| ink2 := base.NewBaseInvoker(url2) |
| ink3 := base.NewBaseInvoker(url3) |
| |
| invokerList := make([]base.Invoker, 0, 3) |
| invokerList = append(invokerList, ink1) |
| invokerList = append(invokerList, ink2) |
| invokerList = append(invokerList, ink3) |
| |
| extension.SetDefaultConfigurator(configurator.NewMockConfigurator) |
| ccURL, _ := common.NewURL("mock://127.0.0.1:1111") |
| mockFactory := &config_center.MockDynamicConfigurationFactory{ |
| Content: ` |
| configVersion: V3.0 |
| scope: application |
| force: true |
| enabled: true |
| runtime: true |
| key: demo-provider |
| conditions: |
| - 'method=sayHello => region=hangzhou'`, |
| } |
| dc, _ := mockFactory.GetDynamicConfiguration(ccURL) |
| commonConfig.GetEnvInstance().SetDynamicConfiguration(dc) |
| |
| router := NewApplicationRouter() |
| router.Notify(invokerList) |
| |
| rpcInvocation := invocation.NewRPCInvocation("sayHello", nil, nil) |
| invokers := router.Route(invokerList, consumerURL, rpcInvocation) |
| assert.Equal(t, 1, len(invokers)) |
| |
| rpcInvocation = invocation.NewRPCInvocation("sayHi", nil, nil) |
| invokers = router.Route(invokerList, consumerURL, rpcInvocation) |
| assert.Equal(t, 3, len(invokers)) |
| } |
| |
| var providerUrls = []string{ |
| "dubbo://127.0.0.1/com.foo.BarService", |
| "dubbo://127.0.0.1/com.foo.BarService", |
| "dubbo://127.0.0.1/com.foo.BarService?env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=gray", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal", |
| "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService", |
| "dubbo://dubbo.apache.org/com.foo.BarService", |
| "dubbo://dubbo.apache.org/com.foo.BarService?env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=gray", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal", |
| "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal", |
| } |
| |
| func buildInvokers() []base.Invoker { |
| res := make([]base.Invoker, 0, len(providerUrls)) |
| for _, url := range providerUrls { |
| u, err := common.NewURL(url) |
| if err != nil { |
| panic(err) |
| } |
| res = append(res, base.NewBaseInvoker(u)) |
| } |
| return res |
| } |
| |
| func Test_parseMultiConditionRoute(t *testing.T) { |
| type args struct { |
| routeContent string |
| } |
| tests := []struct { |
| name string |
| args args |
| want *config.ConditionRouter |
| wantErr assert.ErrorAssertionFunc |
| }{ |
| {name: "testParseConfig", args: args{`configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| |
| ####### |
| conditions: |
| - from: |
| match: tag=tag1 # disable traffic |
| - from: |
| match: tag=gray |
| to: |
| - match: tag!=gray |
| weight: 100 |
| - match: tag=gray |
| weight: 900 |
| - from: |
| match: version=v1 |
| to: |
| - match: version=v1`}, want: &config.ConditionRouter{ |
| Scope: "service", |
| Key: "org.apache.dubbo.samples.CommentService", |
| Force: false, |
| Runtime: true, |
| Enabled: true, |
| Conditions: []*config.ConditionRule{ |
| { |
| From: config.ConditionRuleFrom{Match: "tag=tag1"}, |
| To: nil, |
| }, { |
| From: config.ConditionRuleFrom{ |
| Match: `tag=gray`, |
| }, |
| To: []config.ConditionRuleTo{{ |
| Match: `tag!=gray`, |
| Weight: 100, |
| }, { |
| Match: `tag=gray`, |
| Weight: 900, |
| }}, |
| }, { |
| From: config.ConditionRuleFrom{ |
| Match: `version=v1`, |
| }, |
| To: []config.ConditionRuleTo{{ |
| Match: `version=v1`, |
| Weight: 0, |
| }}, |
| }}, |
| }}, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| got, err := parseMultiConditionRoute(tt.args.routeContent) |
| assert.Nil(t, err) |
| assert.Equalf(t, tt.want, got, "parseMultiConditionRoute(%v)", tt.args.routeContent) |
| }) |
| } |
| } |
| |
| func genMatcher(rule string) FieldMatcher { |
| cond, err := parseRule(rule) |
| if err != nil { |
| panic(err) |
| } |
| m := FieldMatcher{ |
| rule: rule, |
| match: cond, |
| } |
| return m |
| } |
| |
| type InvokersFilters []FieldMatcher |
| |
| func NewINVOKERS_FILTERS() InvokersFilters { |
| return []FieldMatcher{} |
| } |
| |
| func (INV InvokersFilters) add(rule string) InvokersFilters { |
| m := genMatcher(rule) |
| return append(INV, m) |
| } |
| |
| func (INV InvokersFilters) filtrate(inv []base.Invoker, url *common.URL, invocation base.Invocation) []base.Invoker { |
| for _, cond := range INV { |
| tmpInv := make([]base.Invoker, 0) |
| for _, invoker := range inv { |
| if cond.MatchInvoker(url, invoker, invocation) { |
| tmpInv = append(tmpInv, invoker) |
| } |
| } |
| inv = tmpInv |
| } |
| return inv |
| } |
| |
| func newUrl(url string) *common.URL { |
| res, err := common.NewURL(url) |
| if err != nil { |
| panic(err) |
| } |
| return res |
| } |
| |
| func Test_multiplyConditionRoute_route(t *testing.T) { |
| type args struct { |
| invokers []base.Invoker |
| url *common.URL |
| invocation base.Invocation |
| } |
| d := DynamicRouter{ |
| mu: sync.RWMutex{}, |
| force: false, |
| enable: false, |
| conditionRouter: nil, |
| } |
| tests := []struct { |
| name string |
| content string |
| args args |
| invokers_filters InvokersFilters |
| expResLen int |
| multiDestination []struct { |
| invokers_filters InvokersFilters |
| weight float32 |
| } |
| }{ |
| { |
| name: "test base condition", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| invokers_filters: NewINVOKERS_FILTERS().add("env!=gray"), |
| }, { |
| name: "test removeDuplicates condition", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| invokers_filters: NewINVOKERS_FILTERS().add("env!=gray"), |
| }, { |
| name: "test consequent condition", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - from: |
| match: region=beijing |
| to: |
| - match: region=beijing |
| weight: 100 |
| - from: |
| to: |
| - match: host!=127.0.0.1 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| invokers_filters: NewINVOKERS_FILTERS().add("env!=gray").add(`region=beijing`).add(`host!=127.0.0.1`), |
| }, { |
| name: "test unMatch condition", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env!=gray |
| to: |
| - match: env=gray |
| weight: 100 |
| - from: |
| match: region!=beijing |
| to: |
| - match: region=beijing |
| weight: 100 |
| - from: |
| to: |
| - match: host!=127.0.0.1 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| invokers_filters: NewINVOKERS_FILTERS().add(`host!=127.0.0.1`), |
| }, { |
| name: "test Match and Route zero", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: true # <--- |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray # match success here |
| to: |
| - match: env=ErrTag # all invoker can't match this |
| weight: 100 |
| - from: |
| match: region!=beijing |
| to: |
| - match: region=beijing |
| weight: 100 |
| - from: |
| to: |
| - match: host!=127.0.0.1 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| expResLen: 0, |
| }, { |
| name: "test Match, Route zero and ignore ", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false # <--- to ignore bad result |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: region=beijing |
| to: |
| - match: region!=beijing |
| weight: 100 |
| - from: |
| to: |
| - match: host!=127.0.0.1 |
| - from: |
| match: env=gray # match success here |
| to: |
| - match: env=ErrTag # all invoker can't match this |
| weight: 100 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| invokers_filters: NewINVOKERS_FILTERS(), |
| }, { |
| name: "test traffic disabled and ignore condition-route.force", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: host=127.0.0.1 # <--- disabled |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - to: |
| - match: region!=beijing |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| expResLen: 0, |
| }, { |
| name: "test multiply destination", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - match: env=gray |
| weight: 900 |
| - from: |
| match: region=beijing |
| to: |
| - match: region!=beijing |
| weight: 100 |
| - match: region=beijing |
| weight: 200 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| multiDestination: []struct { |
| invokers_filters InvokersFilters |
| weight float32 |
| }{{ |
| invokers_filters: NewINVOKERS_FILTERS().add(`env=gray`).add(`region=beijing`), |
| weight: float32(900) / float32(1000) * float32(200) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region=beijing`), |
| weight: float32(100) / float32(1000) * float32(200) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env=gray`).add(`region!=beijing`), |
| weight: float32(900) / float32(1000) * float32(100) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region!=beijing`), |
| weight: float32(100) / float32(1000) * float32(100) / float32(300), |
| }}, |
| }, { |
| name: "test multiply destination with ignore some condition", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - match: env=gray----error # will ignore this subset |
| weight: 900 |
| - from: |
| match: region=beijing |
| to: |
| - match: region!=beijing |
| weight: 100 |
| - match: region=beijing |
| weight: 200 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| multiDestination: []struct { |
| invokers_filters InvokersFilters |
| weight float32 |
| }{{ |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region=beijing`), |
| weight: float32(200) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region!=beijing`), |
| weight: float32(100) / float32(300), |
| }}, |
| }, { |
| name: "test multiply destination with ignore some condition node", |
| content: `configVersion: v3.1 |
| scope: service |
| key: org.apache.dubbo.samples.CommentService |
| force: false |
| runtime: true |
| enabled: true |
| conditions: |
| - from: |
| match: env=gray |
| to: |
| - match: env!=gray |
| weight: 100 |
| - match: env=gray |
| weight: 900 |
| - from: # <-- will ignore this condition |
| match: region!=beijing |
| to: |
| - match: env=normal |
| weight: 100 |
| - match: env=gray |
| weight: 200 |
| - from: |
| match: region=beijing |
| to: |
| - match: region!=beijing |
| weight: 100 |
| - match: region=beijing |
| weight: 200 |
| `, |
| args: args{ |
| invokers: buildInvokers(), |
| url: newUrl("consumer://127.0.0.1/com.foo.BarService?env=gray®ion=beijing"), |
| invocation: invocation.NewRPCInvocation("echo", nil, nil), |
| }, |
| multiDestination: []struct { |
| invokers_filters InvokersFilters |
| weight float32 |
| }{{ |
| invokers_filters: NewINVOKERS_FILTERS().add(`env=gray`).add(`region=beijing`), |
| weight: float32(900) / float32(1000) * float32(200) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region=beijing`), |
| weight: float32(100) / float32(1000) * float32(200) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env=gray`).add(`region!=beijing`), |
| weight: float32(900) / float32(1000) * float32(100) / float32(300), |
| }, { |
| invokers_filters: NewINVOKERS_FILTERS().add(`env!=gray`).add(`region!=beijing`), |
| weight: float32(100) / float32(1000) * float32(100) / float32(300), |
| }}, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| d.Process(&config_center.ConfigChangeEvent{ |
| Value: tt.content, |
| ConfigType: remoting.EventTypeUpdate, |
| }) |
| if tt.multiDestination == nil { |
| res := d.Route(tt.args.invokers, tt.args.url, tt.args.invocation) |
| if tt.invokers_filters != nil { |
| // check expect filtrate path |
| ans := tt.invokers_filters.filtrate(tt.args.invokers, tt.args.url, tt.args.invocation) |
| assert.Equalf(t, ans, res, "route(%v, %v, %v)", tt.args.invokers, tt.args.url, tt.args.invocation) |
| } else { |
| // check expect result.length |
| assert.Equalf(t, tt.expResLen, len(res), "route(%v, %v, %v)", tt.args.invokers, tt.args.url, tt.args.invocation) |
| } |
| } else { |
| // check multiply destination route successfully or not |
| ans := map[any]float32{} |
| for _, s := range tt.multiDestination { |
| args := struct { |
| invokers []base.Invoker |
| url *common.URL |
| invocation base.Invocation |
| }{tt.args.invokers[:], tt.args.url.Clone(), tt.args.invocation} |
| ans[len(s.invokers_filters.filtrate(args.invokers, tt.args.url, tt.args.invocation))] = s.weight * 1000 |
| } |
| res := map[any]int{} |
| for i := 0; i < 1000; i++ { |
| args := struct { |
| invokers []base.Invoker |
| url *common.URL |
| invocation base.Invocation |
| }{tt.args.invokers[:], tt.args.url.Clone(), tt.args.invocation} |
| res[len(d.Route(args.invokers, args.url, args.invocation))]++ |
| } |
| for k, v := range ans { |
| if float32(res[k]+50) > v && float32(res[k]-50) < v { |
| } else { |
| assert.Fail(t, "out of range") |
| } |
| } |
| } |
| }) |
| } |
| } |