This is an automated email from the ASF dual-hosted git repository.
liujun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/main by this push:
new 4b13d2b86 Ftr: Implement condition routing basic functions and
complete related tests (#2299)
4b13d2b86 is described below
commit 4b13d2b8637a4356b33f992c8208ec7475ef80ec
Author: finalt <[email protected]>
AuthorDate: Tue Apr 18 17:20:59 2023 +0800
Ftr: Implement condition routing basic functions and complete related tests
(#2299)
---
cluster/router/condition/matcher/argument.go | 77 ++++
cluster/router/condition/matcher/base.go | 159 +++++++
cluster/router/condition/matcher/extension.go | 40 ++
cluster/router/condition/matcher/factory.go | 78 ++++
cluster/router/condition/matcher/matcher.go | 52 +++
cluster/router/condition/matcher/param.go | 37 ++
.../condition/matcher/pattern_value/extension.go | 40 ++
.../condition/matcher/pattern_value/pattern.go | 33 ++
.../condition/matcher/pattern_value/wildcard.go | 82 ++++
cluster/router/condition/route.go | 329 ++++++++++++++
cluster/router/condition/router_test.go | 479 +++++++++++++++++++++
common/constant/key.go | 23 +-
12 files changed, 1421 insertions(+), 8 deletions(-)
diff --git a/cluster/router/condition/matcher/argument.go
b/cluster/router/condition/matcher/argument.go
new file mode 100644
index 000000000..f0115f4a7
--- /dev/null
+++ b/cluster/router/condition/matcher/argument.go
@@ -0,0 +1,77 @@
+/*
+ * 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 matcher
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+ argumentsPattern = regexp.MustCompile("arguments\\[([0-9]+)\\]")
+ notFoundArgumentValue = "dubbo internal not found argument condition
value"
+)
+
+// ArgumentConditionMatcher analysis the arguments in the rule.
+// Examples would be like this:
+// "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
+// "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
+type ArgumentConditionMatcher struct {
+ BaseConditionMatcher
+}
+
+func NewArgumentConditionMatcher(key string) *ArgumentConditionMatcher {
+ return &ArgumentConditionMatcher{
+ *NewBaseConditionMatcher(key),
+ }
+}
+
+func (a *ArgumentConditionMatcher) GetValue(sample map[string]string, url
*common.URL, invocation protocol.Invocation) string {
+ // split the rule
+ expressArray := strings.Split(a.key, "\\.")
+ argumentExpress := expressArray[0]
+ matcher := argumentsPattern.FindStringSubmatch(argumentExpress)
+ if len(matcher) == 0 {
+ logger.Warn(notFoundArgumentValue)
+ return ""
+ }
+
+ // extract the argument index
+ index, err := strconv.Atoi(matcher[1])
+ if err != nil {
+ logger.Warn(notFoundArgumentValue)
+ return ""
+ }
+
+ if index < 0 || index > len(invocation.Arguments()) {
+ logger.Warn(notFoundArgumentValue)
+ return ""
+ }
+ return fmt.Sprint(invocation.Arguments()[index])
+}
diff --git a/cluster/router/condition/matcher/base.go
b/cluster/router/condition/matcher/base.go
new file mode 100644
index 000000000..f3858070b
--- /dev/null
+++ b/cluster/router/condition/matcher/base.go
@@ -0,0 +1,159 @@
+/*
+ * 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 matcher
+
+import (
+ "sort"
+ "sync"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+
"dubbo.apache.org/dubbo-go/v3/cluster/router/condition/matcher/pattern_value"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+ valueMatchers = make([]pattern_value.ValuePattern, 0, 8)
+
+ once sync.Once
+)
+
+// BaseConditionMatcher records the match and mismatch patterns of this
matcher while at the same time
+// provides the common match logics.
+type BaseConditionMatcher struct {
+ key string
+ matches map[string]struct{}
+ misMatches map[string]struct{}
+}
+
+func NewBaseConditionMatcher(key string) *BaseConditionMatcher {
+ return &BaseConditionMatcher{
+ key: key,
+ matches: map[string]struct{}{},
+ misMatches: map[string]struct{}{},
+ }
+}
+
+// GetValue returns a value from different places of the request context.
+func (b *BaseConditionMatcher) GetValue(sample map[string]string, url
*common.URL, invocation protocol.Invocation) string {
+ return ""
+}
+
+// IsMatch indicates whether this matcher matches the patterns with request
context.
+func (b *BaseConditionMatcher) IsMatch(value string, param *common.URL,
invocation protocol.Invocation, isWhenCondition bool) bool {
+ if value == "" {
+ // if key does not present in whichever of url, invocation or
attachment based on the matcher type, then return false.
+ return false
+ }
+
+ if len(b.matches) != 0 && len(b.misMatches) == 0 {
+ return b.patternMatches(value, param, invocation,
isWhenCondition)
+ }
+
+ if len(b.misMatches) != 0 && len(b.matches) == 0 {
+ return b.patternMisMatches(value, param, invocation,
isWhenCondition)
+ }
+
+ if len(b.matches) != 0 && len(b.misMatches) != 0 {
+ // when both mismatches and matches contain the same value,
then using mismatches first
+ return b.patternMatches(value, param, invocation,
isWhenCondition) && b.patternMisMatches(value, param, invocation,
isWhenCondition)
+ }
+ return false
+}
+
+// GetMatches returns matches.
+func (b *BaseConditionMatcher) GetMatches() map[string]struct{} {
+ return b.matches
+}
+
+// GetMismatches returns misMatches.
+func (b *BaseConditionMatcher) GetMismatches() map[string]struct{} {
+ return b.misMatches
+}
+
+func (b *BaseConditionMatcher) patternMatches(value string, param *common.URL,
invocation protocol.Invocation, isWhenCondition bool) bool {
+ for match := range b.matches {
+ if doPatternMatch(match, value, param, invocation,
isWhenCondition) {
+ return true
+ }
+ }
+ return false
+}
+
+func (b *BaseConditionMatcher) patternMisMatches(value string, param
*common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+ for mismatch := range b.misMatches {
+ if doPatternMatch(mismatch, value, param, invocation,
isWhenCondition) {
+ return false
+ }
+ }
+ return true
+}
+
+func doPatternMatch(pattern string, value string, url *common.URL, invocation
protocol.Invocation, isWhenCondition bool) bool {
+ once.Do(initValueMatchers)
+ for _, valueMatcher := range valueMatchers {
+ if valueMatcher.ShouldMatch(pattern) {
+ return valueMatcher.Match(pattern, value, url,
invocation, isWhenCondition)
+ }
+ }
+ // If no value matcher is available, will force to use wildcard value
matcher
+ logger.Error("Executing condition rule value match expression error,
will force to use wildcard value matcher")
+
+ valuePattern := pattern_value.GetValuePattern("wildcard")
+ return valuePattern.Match(pattern, value, url, invocation,
isWhenCondition)
+}
+
+// GetSampleValueFromURL returns the value of the conditionKey in the URL
+func GetSampleValueFromURL(conditionKey string, sample map[string]string,
param *common.URL, invocation protocol.Invocation) string {
+ var sampleValue string
+ // get real invoked method name from invocation
+ if invocation != nil && (constant.MethodKey == conditionKey ||
constant.MethodsKey == conditionKey) {
+ sampleValue = invocation.MethodName()
+ } else {
+ sampleValue = sample[conditionKey]
+ }
+ return sampleValue
+}
+
+func Match(condition Matcher, sample map[string]string, param *common.URL,
invocation protocol.Invocation, isWhenCondition bool) bool {
+ return condition.IsMatch(condition.GetValue(sample, param, invocation),
param, invocation, isWhenCondition)
+}
+
+func initValueMatchers() {
+ valuePatterns := pattern_value.GetValuePatterns()
+ for _, valuePattern := range valuePatterns {
+ valueMatchers = append(valueMatchers, valuePattern())
+ }
+ sortValuePattern(valueMatchers)
+}
+
+func sortValuePattern(valuePatterns []pattern_value.ValuePattern) {
+ sort.Stable(byPriority(valuePatterns))
+}
+
+type byPriority []pattern_value.ValuePattern
+
+func (a byPriority) Len() int { return len(a) }
+func (a byPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a byPriority) Less(i, j int) bool { return a[i].Priority() <
a[j].Priority() }
diff --git a/cluster/router/condition/matcher/extension.go
b/cluster/router/condition/matcher/extension.go
new file mode 100644
index 000000000..7bccd30a3
--- /dev/null
+++ b/cluster/router/condition/matcher/extension.go
@@ -0,0 +1,40 @@
+/*
+ * 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 matcher
+
+var (
+ matchers = make(map[string]func() ConditionMatcherFactory)
+)
+
+// SetMatcherFactory sets create matcherFactory function with @name
+func SetMatcherFactory(name string, fun func() ConditionMatcherFactory) {
+ matchers[name] = fun
+}
+
+// GetMatcherFactory gets create matcherFactory function by name
+func GetMatcherFactory(name string) ConditionMatcherFactory {
+ if matchers[name] == nil {
+ panic("matcher_factory for " + name + " is not existing, make
sure you have imported the package.")
+ }
+ return matchers[name]()
+}
+
+// GetMatcherFactories gets all create matcherFactory function
+func GetMatcherFactories() map[string]func() ConditionMatcherFactory {
+ return matchers
+}
diff --git a/cluster/router/condition/matcher/factory.go
b/cluster/router/condition/matcher/factory.go
new file mode 100644
index 000000000..f5cc89476
--- /dev/null
+++ b/cluster/router/condition/matcher/factory.go
@@ -0,0 +1,78 @@
+/*
+ * 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 matcher
+
+import (
+ "math"
+ "strings"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func init() {
+ SetMatcherFactory("argument", NewArgumentMatcherFactory)
+ SetMatcherFactory("param", NewParamMatcherFactory)
+}
+
+// ArgumentMatcherFactory matcher factory
+type ArgumentMatcherFactory struct {
+}
+
+// NewArgumentMatcherFactory constructs a new argument.ArgumentMatcherFactory
+func NewArgumentMatcherFactory() ConditionMatcherFactory {
+ return &ArgumentMatcherFactory{}
+}
+
+func (a *ArgumentMatcherFactory) ShouldMatch(key string) bool {
+ return strings.HasPrefix(key, constant.Arguments)
+}
+
+// NewMatcher constructs a new matcher
+func (a *ArgumentMatcherFactory) NewMatcher(key string) Matcher {
+ return NewArgumentConditionMatcher(key)
+}
+
+func (a *ArgumentMatcherFactory) Priority() int64 {
+ return 300
+}
+
+// ParamMatcherFactory matcher factory
+type ParamMatcherFactory struct {
+}
+
+// NewParamMatcherFactory constructs a new paramMatcherFactory
+func NewParamMatcherFactory() ConditionMatcherFactory {
+ return &ParamMatcherFactory{}
+}
+
+func (p *ParamMatcherFactory) ShouldMatch(key string) bool {
+ return true
+}
+
+// NewMatcher constructs a new matcher
+func (p *ParamMatcherFactory) NewMatcher(key string) Matcher {
+ return NewParamConditionMatcher(key)
+}
+
+// Priority make sure this is the last matcher being executed.
+// This instance will be loaded separately to ensure it always gets executed
as the last matcher.
+func (p *ParamMatcherFactory) Priority() int64 {
+ return math.MaxInt64
+}
diff --git a/cluster/router/condition/matcher/matcher.go
b/cluster/router/condition/matcher/matcher.go
new file mode 100644
index 000000000..aeddb4547
--- /dev/null
+++ b/cluster/router/condition/matcher/matcher.go
@@ -0,0 +1,52 @@
+/*
+ * 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 matcher
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ConditionMatcherFactory interface {
+ // ShouldMatch indicates whether the key is of the form of the current
matcher type which this factory instance represents.
+ ShouldMatch(key string) bool
+ // NewMatcher returns a matcher instance for the key.
+ NewMatcher(key string) Matcher
+ // Priority returns Priority in ConditionMatcherFactory
+ // 0 to ^int(0) is better, smaller value by better priority.
+ Priority() int64
+}
+
+// Matcher represents a specific match condition of a condition rule.
+// The following condition rule 'foo=bar&arguments[0]=hello* =>
region=hangzhou' consists of three ConditionMatchers:
+// 1. ParamConditionMatcher represented by 'foo=bar'
+// 2. ArgumentConditionMatcher represented by 'arguments[0]=hello*'
+// 3. ParamConditionMatcher represented by 'region=hangzhou'
+type Matcher interface {
+ // IsMatch indicates whether this matcher matches the patterns with
request context.
+ IsMatch(value string, param *common.URL, invocation
protocol.Invocation, isWhenCondition bool) bool
+ // GetMatches returns matches.
+ // match patterns extracted from when condition.
+ GetMatches() map[string]struct{}
+ // GetMismatches returns misMatches.
+ // mismatch patterns extracted from then condition.
+ GetMismatches() map[string]struct{}
+ // GetValue returns a value from different places of the request
context, for example, url, attachment and invocation.
+ // This makes condition rule possible to check values in any place of a
request.
+ GetValue(sample map[string]string, url *common.URL, invocation
protocol.Invocation) string
+}
diff --git a/cluster/router/condition/matcher/param.go
b/cluster/router/condition/matcher/param.go
new file mode 100644
index 000000000..bc4be3075
--- /dev/null
+++ b/cluster/router/condition/matcher/param.go
@@ -0,0 +1,37 @@
+/*
+ * 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 matcher
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ParamConditionMatcher struct {
+ BaseConditionMatcher
+}
+
+func NewParamConditionMatcher(key string) *ParamConditionMatcher {
+ return &ParamConditionMatcher{
+ *NewBaseConditionMatcher(key),
+ }
+}
+
+func (p *ParamConditionMatcher) GetValue(sample map[string]string, url
*common.URL, invocation protocol.Invocation) string {
+ return GetSampleValueFromURL(p.key, sample, url, invocation)
+}
diff --git a/cluster/router/condition/matcher/pattern_value/extension.go
b/cluster/router/condition/matcher/pattern_value/extension.go
new file mode 100644
index 000000000..a9116d2a6
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/extension.go
@@ -0,0 +1,40 @@
+/*
+ * 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 pattern_value
+
+var (
+ valuePatterns = make(map[string]func() ValuePattern)
+)
+
+// SetValuePattern sets create valuePattern function with @name
+func SetValuePattern(name string, fun func() ValuePattern) {
+ valuePatterns[name] = fun
+}
+
+// GetValuePattern gets create valuePattern function by name
+func GetValuePattern(name string) ValuePattern {
+ if valuePatterns[name] == nil {
+ panic("value_pattern for " + name + " is not existing, make
sure you have imported the package.")
+ }
+ return valuePatterns[name]()
+}
+
+// GetValuePatterns gets all create valuePattern function
+func GetValuePatterns() map[string]func() ValuePattern {
+ return valuePatterns
+}
diff --git a/cluster/router/condition/matcher/pattern_value/pattern.go
b/cluster/router/condition/matcher/pattern_value/pattern.go
new file mode 100644
index 000000000..743719c89
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/pattern.go
@@ -0,0 +1,33 @@
+/*
+ * 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 pattern_value
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+type ValuePattern interface {
+ // ShouldMatch indicates whether the input is a specific pattern, for
example, range pattern '1~100', wildcard pattern 'hello*', etc.
+ ShouldMatch(pattern string) bool
+ // Match indicates whether a pattern is matched with the request context
+ Match(pattern string, value string, url *common.URL, invocation
protocol.Invocation, isWhenCondition bool) bool
+ // Priority returns a priority for this valuePattern
+ // 0 to ^int(0) is better, smaller value by better priority
+ Priority() int64
+}
diff --git a/cluster/router/condition/matcher/pattern_value/wildcard.go
b/cluster/router/condition/matcher/pattern_value/wildcard.go
new file mode 100644
index 000000000..dff572107
--- /dev/null
+++ b/cluster/router/condition/matcher/pattern_value/wildcard.go
@@ -0,0 +1,82 @@
+/*
+ * 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 pattern_value
+
+import (
+ "math"
+ "strings"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+// WildcardValuePattern evaluator must be the last one being executed.
+// matches with patterns like 'key=hello', 'key=hello*', 'key=*hello',
'key=h*o' or 'key=*'
+type WildcardValuePattern struct {
+}
+
+func init() {
+ SetValuePattern("wildcard", NewValuePattern)
+}
+
+func NewValuePattern() ValuePattern {
+ return &WildcardValuePattern{}
+}
+
+func (w *WildcardValuePattern) Priority() int64 {
+ return math.MaxInt64
+}
+
+func (w *WildcardValuePattern) ShouldMatch(pattern string) bool {
+ return true
+}
+
+func (w *WildcardValuePattern) Match(pattern string, value string, url
*common.URL, invocation protocol.Invocation, isWhenCondition bool) bool {
+ if url != nil && strings.HasPrefix(pattern, "$") {
+ pattern = url.GetRawParam(pattern[1:])
+ }
+
+ if "*" == pattern {
+ return true
+ }
+ if pattern == "" && value == "" {
+ return true
+ }
+ if pattern == "" || value == "" {
+ return false
+ }
+
+ i := strings.LastIndex(pattern, "*")
+ // doesn't find "*"
+ if i == -1 {
+ return value == pattern
+ } else if i == len(pattern)-1 {
+ // "*" is at the end
+ return strings.HasPrefix(value, pattern[0:i])
+ } else if i == 0 {
+ // "*" is at the beginning
+ return strings.HasSuffix(value, pattern[i+1:])
+ } else {
+ // "*" is in the middle
+ prefix := pattern[0:i]
+ suffix := pattern[i+1:]
+ return strings.HasPrefix(value, prefix) &&
strings.HasSuffix(value, suffix)
+ }
+}
diff --git a/cluster/router/condition/route.go
b/cluster/router/condition/route.go
new file mode 100644
index 000000000..ea046020d
--- /dev/null
+++ b/cluster/router/condition/route.go
@@ -0,0 +1,329 @@
+/*
+ * 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 (
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+
+ "github.com/pkg/errors"
+
+ "gopkg.in/yaml.v2"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/cluster/router/condition/matcher"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/config"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+)
+
+var (
+ routePattern = regexp.MustCompile("([&!=,]*)\\s*([^&!=,\\s]+)")
+
+ illegalMsg = "Illegal route rule \"%s\", The error char '%s' before
'%s'"
+
+ matcherFactories = make([]matcher.ConditionMatcherFactory, 0, 8)
+
+ once sync.Once
+)
+
+type StateRouter struct {
+ enable bool
+ force bool
+ url *common.URL
+ whenCondition map[string]matcher.Matcher
+ thenCondition map[string]matcher.Matcher
+}
+
+func NewConditionStateRouter(url *common.URL) (*StateRouter, error) {
+ once.Do(initMatcherFactories)
+
+ if len(matcherFactories) == 0 {
+ return nil, errors.Errorf("No ConditionMatcherFactory exists")
+ }
+
+ force := url.GetParamBool(constant.ForceKey, false)
+ enable := url.GetParamBool(constant.EnabledKey, true)
+ c := &StateRouter{
+ url: url,
+ force: force,
+ enable: enable,
+ }
+
+ if enable {
+ when, then, err := generateMatcher(url)
+ if err != nil {
+ return nil, err
+ }
+ c.whenCondition = when
+ c.thenCondition = then
+ }
+ return c, nil
+}
+
+// Route Determine the target invokers list.
+func (c *StateRouter) Route(invokers []protocol.Invoker, url *common.URL,
invocation protocol.Invocation) []protocol.Invoker {
+ if !c.enable {
+ return invokers
+ }
+
+ if len(invokers) == 0 {
+ return invokers
+ }
+
+ if !c.matchWhen(url, invocation) {
+ return invokers
+ }
+
+ if len(c.thenCondition) == 0 {
+ logger.Warn("condition state router thenCondition is empty")
+ return []protocol.Invoker{}
+ }
+
+ var result = make([]protocol.Invoker, 0, len(invokers))
+ for _, invoker := range invokers {
+ if c.matchThen(invoker.GetURL(), url) {
+ result = append(result, invoker)
+ }
+ }
+
+ if len(result) != 0 {
+ return result
+ } else if c.force {
+ logger.Warn("execute condition state router result list is
empty. and force=true")
+ return result
+ }
+
+ return invokers
+}
+
+func (c *StateRouter) URL() *common.URL {
+ return c.url
+}
+
+func (c *StateRouter) Priority() int64 {
+ return 0
+}
+
+func (c *StateRouter) matchWhen(url *common.URL, invocation
protocol.Invocation) bool {
+ if len(c.whenCondition) == 0 {
+ return true
+ }
+ return doMatch(url, nil, invocation, c.whenCondition, true)
+}
+
+func (c *StateRouter) matchThen(url *common.URL, param *common.URL) bool {
+ if len(c.thenCondition) == 0 {
+ return false
+ }
+ return doMatch(url, param, nil, c.thenCondition, false)
+}
+
+func generateMatcher(url *common.URL) (when, then map[string]matcher.Matcher,
err error) {
+ rule := url.GetParam(constant.RuleKey, "")
+ if rule == "" || len(strings.Trim(rule, " ")) == 0 {
+ return nil, nil, errors.Errorf("Illegal route rule!")
+ }
+ rule = strings.Replace(rule, "consumer.", "", -1)
+ rule = strings.Replace(rule, "provider.", "", -1)
+ i := strings.Index(rule, "=>")
+ // for the case of `{when rule} => {then rule}`
+ var whenRule string
+ var thenRule string
+ if i < 0 {
+ whenRule = ""
+ thenRule = strings.Trim(rule, " ")
+ } else {
+ whenRule = strings.Trim(rule[0:i], " ")
+ thenRule = strings.Trim(rule[i+2:], " ")
+ }
+
+ when, err = parseWhen(whenRule)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ then, err = parseThen(thenRule)
+ if err != nil {
+ return nil, nil, err
+ }
+ // NOTE: It should be determined on the business level whether the
`When condition` can be empty or not.
+ return when, then, nil
+}
+
+func parseWhen(whenRule string) (map[string]matcher.Matcher, error) {
+ if whenRule == "" || whenRule == " " || whenRule == "true" {
+ return make(map[string]matcher.Matcher), nil
+ } else {
+ when, err := parseRule(whenRule)
+ if err != nil {
+ return nil, err
+ }
+ return when, nil
+ }
+}
+
+func parseThen(thenRule string) (map[string]matcher.Matcher, error) {
+ if thenRule == "" || thenRule == " " || thenRule == "false" {
+ return nil, nil
+ } else {
+ when, err := parseRule(thenRule)
+ if err != nil {
+ return nil, err
+ }
+ return when, nil
+ }
+}
+
+func parseRule(rule string) (map[string]matcher.Matcher, error) {
+ if isRuleEmpty(rule) {
+ return make(map[string]matcher.Matcher), nil
+ }
+
+ condition, err := processMatchers(rule)
+ if err != nil {
+ return nil, err
+ }
+
+ return condition, nil
+}
+
+func isRuleEmpty(rule string) bool {
+ return rule == "" || (len(rule) == 1 && rule[0] == ' ')
+}
+
+func processMatchers(rule string) (map[string]matcher.Matcher, error) {
+ condition := make(map[string]matcher.Matcher)
+ var currentMatcher matcher.Matcher
+ var err error
+ values := make(map[string]struct{})
+
+ for _, matchers := range routePattern.FindAllStringSubmatch(rule, -1) {
+ separator := matchers[1]
+ content := matchers[2]
+
+ switch separator {
+ case "":
+ currentMatcher = getMatcher(content)
+ condition[content] = currentMatcher
+ case "&":
+ currentMatcher, condition =
processAndSeparator(content, condition)
+ case "=", "!=":
+ values, currentMatcher, err =
processEqualNotEqualSeparator(separator, content, currentMatcher, rule)
+ if err != nil {
+ return nil, err
+ }
+ case ",":
+ values, err = processCommaSeparator(content, values,
rule)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, errors.Errorf(illegalMsg, rule, separator,
content)
+ }
+ }
+
+ return condition, nil
+}
+
+func processAndSeparator(content string, condition map[string]matcher.Matcher)
(matcher.Matcher, map[string]matcher.Matcher) {
+ currentMatcher := condition[content]
+ if currentMatcher == nil {
+ currentMatcher = getMatcher(content)
+ condition[content] = currentMatcher
+ }
+ return currentMatcher, condition
+}
+
+func processEqualNotEqualSeparator(separator, content string, currentMatcher
matcher.Matcher, rule string) (map[string]struct{}, matcher.Matcher, error) {
+ if currentMatcher == nil {
+ return nil, nil, errors.Errorf(illegalMsg, rule, separator,
content)
+ }
+ values := currentMatcher.GetMatches()
+ if separator == "!=" {
+ values = currentMatcher.GetMismatches()
+ }
+ values[content] = struct{}{}
+ return values, currentMatcher, nil
+}
+
+func processCommaSeparator(content string, values map[string]struct{}, rule
string) (map[string]struct{}, error) {
+ if len(values) == 0 {
+ return nil, errors.Errorf(illegalMsg, rule, ",", content)
+ }
+ values[content] = struct{}{}
+ return values, nil
+}
+
+func getMatcher(key string) matcher.Matcher {
+ for _, factory := range matcherFactories {
+ if factory.ShouldMatch(key) {
+ return factory.NewMatcher(key)
+ }
+ }
+ return matcher.GetMatcherFactory("param").NewMatcher(key)
+}
+
+func doMatch(url *common.URL, param *common.URL, invocation
protocol.Invocation, conditions map[string]matcher.Matcher, isWhenCondition
bool) bool {
+ sample := url.ToMap()
+ for _, matcherPair := range conditions {
+ if !matcher.Match(matcherPair, sample, param, invocation,
isWhenCondition) {
+ return false
+ }
+ }
+ return true
+}
+
+func initMatcherFactories() {
+ factoriesMap := matcher.GetMatcherFactories()
+ if len(factoriesMap) == 0 {
+ return
+ }
+ for _, factory := range factoriesMap {
+ matcherFactories = append(matcherFactories, factory())
+ }
+ sortMatcherFactories(matcherFactories)
+}
+
+func sortMatcherFactories(matcherFactories []matcher.ConditionMatcherFactory) {
+ sort.Stable(byPriority(matcherFactories))
+}
+
+type byPriority []matcher.ConditionMatcherFactory
+
+func (a byPriority) Len() int { return len(a) }
+func (a byPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a byPriority) Less(i, j int) bool { return a[i].Priority() <
a[j].Priority() }
+
+func parseRoute(routeContent string) (*config.RouterConfig, error) {
+ routeDecoder := yaml.NewDecoder(strings.NewReader(routeContent))
+ routerConfig := &config.RouterConfig{}
+ err := routeDecoder.Decode(routerConfig)
+ if err != nil {
+ return nil, err
+ }
+ return routerConfig, nil
+}
diff --git a/cluster/router/condition/router_test.go
b/cluster/router/condition/router_test.go
new file mode 100644
index 000000000..6b264e033
--- /dev/null
+++ b/cluster/router/condition/router_test.go
@@ -0,0 +1,479 @@
+/*
+ * 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 (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+)
+
+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"
+)
+
+func TestRouteMatchWhen(t *testing.T) {
+
+ rpcInvocation := invocation.NewRPCInvocation("getFoo", 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("getFoo", nil, nil)
+
+ ink1 := protocol.NewBaseInvoker(url1)
+ ink2 := protocol.NewBaseInvoker(url2)
+ ink3 := protocol.NewBaseInvoker(url3)
+
+ invokerList := make([]protocol.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("getFoo", 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, match",
+ 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("getFoo", nil, nil)
+ consumerURL, _ := common.NewURL(localConsumerAddr)
+
+ testData := []struct {
+ name string
+ urls []string
+ rule string
+
+ wantUrls []string
+ wantVal int
+ }{
+ {
+ name: "ReturnFalse",
+ urls: []string{
+ "",
+ "",
+ "",
+ },
+ rule: "host = 127.0.0.1 => false",
+
+ wantUrls: []string{},
+ wantVal: 0,
+ },
+ {
+ name: "ReturnEmpty",
+ urls: []string{
+ "",
+ "",
+ "",
+ },
+ 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([]protocol.Invoker, 0, len(data.urls))
+ for _, urlStr := range data.urls {
+ url, _ := common.NewURL(urlStr)
+ invoker := protocol.NewBaseInvoker(url)
+ invokers = append(invokers, invoker)
+ }
+
+ wantInvokers := make([]protocol.Invoker, 0,
len(data.wantUrls))
+ for _, wantUrlStr := range data.wantUrls {
+ url, _ := common.NewURL(wantUrlStr)
+ invoker := protocol.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 := protocol.NewBaseInvoker(url1)
+ ink2 := protocol.NewBaseInvoker(url2)
+ ink3 := protocol.NewBaseInvoker(url3)
+
+ invokerList := make([]protocol.Invoker, 0, 3)
+ invokerList = append(invokerList, ink1)
+ invokerList = append(invokerList, ink2)
+ invokerList = append(invokerList, ink3)
+
+ consumerURL, _ := common.NewURL(localConsumerAddr)
+
+ testData := []struct {
+ name string
+ argument interface{}
+ 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([]interface{}, 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)
+
+ })
+ }
+}
diff --git a/common/constant/key.go b/common/constant/key.go
index 783eae098..131b200ae 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -296,14 +296,21 @@ const (
// Use for router module
const (
- TagRouterRuleSuffix = ".tag-router"
- ConditionRouterRuleSuffix = ".condition-router" // Specify
condition router suffix
- MeshRouteSuffix = ".MESHAPPRULE" // Specify mesh
router suffix
- ForceUseTag = "dubbo.force.tag" // the tag in
attachment
- Tagkey = "dubbo.tag" // key of tag
- AttachmentKey = DubboCtxKey("attachment") // key in context
in invoker
- TagRouterFactoryKey = "tag"
- MeshRouterFactoryKey = "mesh"
+ TagRouterRuleSuffix = ".tag-router"
+ ConditionRouterRuleSuffix = ".condition-router" // Specify
condition router suffix
+ MeshRouteSuffix = ".MESHAPPRULE" // Specify mesh
router suffix
+ ForceUseTag = "dubbo.force.tag" // the tag in
attachment
+ ForceUseCondition = "dubbo.force.condition"
+ Tagkey = "dubbo.tag" // key of tag
+ ConditionKey = "dubbo.condition"
+ AttachmentKey = DubboCtxKey("attachment") // key in
context in invoker
+ TagRouterFactoryKey = "tag"
+ ConditionAppRouterFactoryKey = "provider.condition"
+ ConditionServiceRouterFactoryKey = "service.condition"
+ ForceKey = "force"
+ Arguments = "arguments"
+ Attachments = "attachments"
+ MeshRouterFactoryKey = "mesh"
)
// Auth filter