blob: 605854c08e8e72c04fffb8397219f6662ee589c7 [file] [log] [blame]
/*
* 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&region=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&region=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&region=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&region=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&region=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&region=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&region=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&region=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&region=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&region=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&region=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")
}
}
}
})
}
}