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


Reply via email to