diff --git a/.gitignore b/.gitignore index e5ba291004ab0d89c1ef1db6f353361232fddcc8..568e9f24541dd6f02dd8670436fd48db481b7f21 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ coverage.txt remoting/zookeeper/zookeeper-4unittest/ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ -registry/consul/agent* \ No newline at end of file +registry/consul/agent* +config_center/apollo/mockDubbog.properties.json diff --git a/.travis.yml b/.travis.yml index 707e64481416b2c090bad05cddce2b3ccebf4535..7f30febe7bbd95ffbf1c25abce997408c7681074 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ language: go +os: + - linux + - osx + go: - - "1.12" + - "1.13" env: - GO111MODULE=on @@ -10,10 +14,7 @@ install: true script: - go fmt ./... && [[ -z `git status -s` ]] - - mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar - - wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar - - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ - - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + - chmod u+x before_ut.sh && ./before_ut.sh - go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic after_success: diff --git a/CHANGE.md b/CHANGE.md index 947a695ca854fe8c3d91d8ea989b52dcddbe1523..ea2fe351cfffb81e0f89f340f402ce218b32fbf1 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,64 +1,98 @@ # Release Notes +--- + +## 1.3.0 + +### New Features + +- [Add apollo config center support](https://github.com/apache/dubbo-go/pull/250) +- [Gracefully shutdown](https://github.com/apache/dubbo-go/pull/255) +- [Add consistent hash load balance support](https://github.com/apache/dubbo-go/pull/261) +- [Add sticky connection support](https://github.com/apache/dubbo-go/pull/270) +- [Add async call for dubbo protocol](https://github.com/apache/dubbo-go/pull/272) +- [Add generic implement](https://github.com/apache/dubbo-go/pull/291) +- [Add request timeout for method](https://github.com/apache/dubbo-go/pull/284) +- [Add grpc protocol](https://github.com/apache/dubbo-go/pull/311) + +### Enhancement + +- [The SIGSYS and SIGSTOP are not supported in windows platform](https://github.com/apache/dubbo-go/pull/262) +- [Error should be returned when `NewURL` failed](https://github.com/apache/dubbo-go/pull/266) +- [Split config center GetConfig method](https://github.com/apache/dubbo-go/pull/267) +- [Modify closing method for dubbo protocol](https://github.com/apache/dubbo-go/pull/268) +- [Add SetLoggerLevel method](https://github.com/apache/dubbo-go/pull/271) +- [Change the position of the lock](https://github.com/apache/dubbo-go/pull/286) +- [Change zk version and add base_registry](https://github.com/apache/dubbo-go/pull/355) + +### Bugfixes + +- [Fix negative wait group count](https://github.com/apache/dubbo-go/pull/253) +- [After disconnection with ZK registry, cosumer can't listen to provider changes](https://github.com/apache/dubbo-go/pull/258) +- [The generic filter and default reference filters lack ','](https://github.com/apache/dubbo-go/pull/260) +- [Url encode zkpath](https://github.com/apache/dubbo-go/pull/283) +- [Fix jsonrpc about HTTP/1.1](https://github.com/apache/dubbo-go/pull/327) +- [Fix zk bug](https://github.com/apache/dubbo-go/pull/346) +- [HessianCodec failed to check package header length](https://github.com/apache/dubbo-go/pull/381) ## 1.2.0 ### New Features -- Add etcdv3 registry support<https://github.com/apache/dubbo-go/pull/148> -- Add nacos registry support<https://github.com/apache/dubbo-go/pull/151> -- Add fail fast cluster support<https://github.com/apache/dubbo-go/pull/140> -- Add available cluster support<https://github.com/apache/dubbo-go/pull/155> -- Add broadcast cluster support<https://github.com/apache/dubbo-go/pull/158> -- Add forking cluster support<https://github.com/apache/dubbo-go/pull/161> -- Add service token authorization support<https://github.com/apache/dubbo-go/pull/202> -- Add accessLog filter support<https://github.com/apache/dubbo-go/pull/214> -- Add tps limit support<https://github.com/apache/dubbo-go/pull/237> -- Add execute limit support<https://github.com/apache/dubbo-go/pull/246> -- Move callService to invoker & support attachments<https://github.com/apache/dubbo-go/pull/193> -- Move example in dubbo-go project away<https://github.com/apache/dubbo-go/pull/228> -- Support dynamic config center which compatible with dubbo 2.6.x & 2.7.x and commit the zookeeper impl<https://github.com/apache/dubbo-go/pull/194> +- [Add etcdv3 registry support](https://github.com/apache/dubbo-go/pull/148) +- [Add nacos registry support](https://github.com/apache/dubbo-go/pull/151) +- [Add fail fast cluster support](https://github.com/apache/dubbo-go/pull/140) +- [Add available cluster support](https://github.com/apache/dubbo-go/pull/155) +- [Add broadcast cluster support](https://github.com/apache/dubbo-go/pull/158) +- [Add forking cluster support](https://github.com/apache/dubbo-go/pull/161) +- [Add service token authorization support](https://github.com/apache/dubbo-go/pull/202) +- [Add accessLog filter support](https://github.com/apache/dubbo-go/pull/214) +- [Add tps limit support](https://github.com/apache/dubbo-go/pull/237) +- [Add execute limit support](https://github.com/apache/dubbo-go/pull/246) +- [Move callService to invoker & support attachments](https://github.com/apache/dubbo-go/pull/193) +- [Move example in dubbo-go project away](https://github.com/apache/dubbo-go/pull/228) +- [Support dynamic config center which compatible with dubbo 2.6.x & 2.7.x and commit the zookeeper impl](https://github.com/apache/dubbo-go/pull/194) ### Enhancement -- Split gettyRPCClient.close and gettyRPCClientPool.remove in protocol/dubbo/pool.go<https://github.com/apache/dubbo-go/pull/186> -- Remove client from pool before closing it<https://github.com/apache/dubbo-go/pull/190> -- Enhance the logic for fetching the local address<https://github.com/apache/dubbo-go/pull/209> -- Add protocol_conf default values<https://github.com/apache/dubbo-go/pull/221> -- Add task pool for getty<https://github.com/apache/dubbo-go/pull/141> -- Update getty: remove read queue<https://github.com/apache/dubbo-go/pull/137> -- Clean heartbeat from PendingResponse<https://github.com/apache/dubbo-go/pull/166> +- [Split gettyRPCClient.close and gettyRPCClientPool.remove in protocol/dubbo/pool.go](https://github.com/apache/dubbo-go/pull/186) +- [Remove client from pool before closing it](https://github.com/apache/dubbo-go/pull/190) +- [Enhance the logic for fetching the local address](https://github.com/apache/dubbo-go/pull/209) +- [Add protocol_conf default values](https://github.com/apache/dubbo-go/pull/221) +- [Add task pool for getty](https://github.com/apache/dubbo-go/pull/141) +- [Update getty: remove read queue](https://github.com/apache/dubbo-go/pull/137) +- [Clean heartbeat from PendingResponse](https://github.com/apache/dubbo-go/pull/166) ### Bugfixes -- GettyRPCClientPool remove deadlock<https://github.com/apache/dubbo-go/pull/183/files> -- Fix failover cluster bug and url parameter retries change int to string type<https://github.com/apache/dubbo-go/pull/195> -- Fix url params unsafe map<https://github.com/apache/dubbo-go/pull/201> -- Read protocol config by map key in config yaml instead of protocol name<https://github.com/apache/dubbo-go/pull/218> -- Fix dubbo group issues #238<https://github.com/apache/dubbo-go/pull/243>/<https://github.com/apache/dubbo-go/pull/244> -- Fix bug in reference_config<https://github.com/apache/dubbo-go/pull/157> -- Fix high memory bug in zookeeper listener<https://github.com/apache/dubbo-go/pull/168> +- [GettyRPCClientPool remove deadlock](https://github.com/apache/dubbo-go/pull/183/files) +- [Fix failover cluster bug and url parameter retries change int to string type](https://github.com/apache/dubbo-go/pull/195) +- [Fix url params unsafe map](https://github.com/apache/dubbo-go/pull/201) +- [Read protocol config by map key in config yaml instead of protocol name](https://github.com/apache/dubbo-go/pull/218) +- *Fix dubbo group issues #238* [pr #243](https://github.com/apache/dubbo-go/pull/243) and [pr #244](https://github.com/apache/dubbo-go/pull/244) +- [Fix bug in reference_config](https://github.com/apache/dubbo-go/pull/157) +- [Fix high memory bug in zookeeper listener](https://github.com/apache/dubbo-go/pull/168) ## 1.1.0 ### New Features -- Support Java bigdecimal<https://github.com/apache/dubbo-go/pull/126>锛� -- Support all JDK exceptions<https://github.com/apache/dubbo-go/pull/120>锛� -- Support multi-version of service<https://github.com/apache/dubbo-go/pull/119>锛� -- Allow user set custom params for registry<https://github.com/apache/dubbo-go/pull/117>锛� -- Support zookeeper config center<https://github.com/apache/dubbo-go/pull/99>锛� -- Failsafe/Failback Cluster Strategy<https://github.com/apache/dubbo-go/pull/136>; +- [Support Java bigdecimal](https://github.com/apache/dubbo-go/pull/126) +- [Support all JDK exceptions](https://github.com/apache/dubbo-go/pull/120) +- [Support multi-version of service](https://github.com/apache/dubbo-go/pull/119) +- [Allow user set custom params for registry](https://github.com/apache/dubbo-go/pull/117) +- [Support zookeeper config center](https://github.com/apache/dubbo-go/pull/99) +- [Failsafe/Failback Cluster Strategy](https://github.com/apache/dubbo-go/pull/136) ### Enhancement -- Use time wheel instead of time.After to defeat timer object memory leakage<https://github.com/apache/dubbo-go/pull/130> 锛� +- [Use time wheel instead of time.After to defeat timer object memory leakage](https://github.com/apache/dubbo-go/pull/130) ### Bugfixes -- Preventing dead loop when got zookeeper unregister event<https://github.com/apache/dubbo-go/pull/129>锛� -- Delete ineffassign<https://github.com/apache/dubbo-go/pull/127>锛� -- Add wg.Done() for mockDataListener<https://github.com/apache/dubbo-go/pull/118>锛� -- Delete wrong spelling words<https://github.com/apache/dubbo-go/pull/107>锛� -- Use sync.Map to defeat from gettyClientPool deadlock<https://github.com/apache/dubbo-go/pull/106>锛� -- Handle panic when function args list is empty<https://github.com/apache/dubbo-go/pull/98>锛� -- url.Values is not safe map<https://github.com/apache/dubbo-go/pull/172>; +- [Preventing dead loop when got zookeeper unregister event](https://github.com/apache/dubbo-go/pull/129) +- [Delete ineffassign](https://github.com/apache/dubbo-go/pull/127) +- [Add wg.Done() for mockDataListener](https://github.com/apache/dubbo-go/pull/118) +- [Delete wrong spelling words](https://github.com/apache/dubbo-go/pull/107) +- [Use sync.Map to defeat from gettyClientPool deadlock](https://github.com/apache/dubbo-go/pull/106) +- [Handle panic when function args list is empty](https://github.com/apache/dubbo-go/pull/98) +- [url.Values is not safe map](https://github.com/apache/dubbo-go/pull/172) diff --git a/README.md b/README.md index 2030664dba256e005d8b43c4098e42e04cac6fc1..1dde951d350e6ee51f3f2aeeac7bb516b1b999be 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,13 @@ Apache License, Version 2.0 ## Release note ## -[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) +[v1.3.0 - Mar 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.3.0) + +[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) [v1.1.0 - Sep 7, 2019 the first release after transferred to apache](https://github.com/apache/dubbo-go/releases/tag/v1.1.0) -[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) +[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## Project Architecture ## @@ -43,18 +45,18 @@ Finished List: - Codec * JsonRPC V2 * Hessian V2 - + - Protocol * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) - + - Registry * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) - + - Dynamic Configure Center & Service Management Configurator * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) @@ -66,12 +68,13 @@ Finished List: * [Available](https://github.com/apache/dubbo-go/pull/155) * [Broadcast](https://github.com/apache/dubbo-go/pull/158) * [Forking](https://github.com/apache/dubbo-go/pull/161) - + - Load Balance * Random * [RoundRobin](https://github.com/apache/dubbo-go/pull/66) * [LeastActive](https://github.com/apache/dubbo-go/pull/65) - + * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261) + - Filter * Echo Health Check * [Circuit break and service downgrade](https://github.com/apache/dubbo-go/pull/133) @@ -80,10 +83,10 @@ Finished List: * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) * [GenericServiceFilter](https://github.com/apache/dubbo-go/pull/291) - + - Invoke * [generic invoke](https://github.com/apache/dubbo-go/pull/122) - + - Others: * start check * connecting certain provider @@ -94,14 +97,13 @@ Finished List: Working List: -- Load Balance: ConsistentHash - Registry: k8s - Metadata Center (dubbo v2.7.x) - Metrics: Opentracing/Promethus(dubbo v2.7.x) You can know more about dubbo-go by its [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap). - + ## Document @@ -125,7 +127,7 @@ Windows before_ut.bat ``` -# Run +### Run ```bash go test ./... @@ -133,6 +135,10 @@ go test ./... go test ./... -coverprofile=coverage.txt -covermode=atomic ``` +## Build + +Please move to [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) + ## Contributing If you are willing to do some code contributions and document contributions to [Apache/dubbo-go](https://github.com/apache/dubbo-go), please visit [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md). @@ -147,11 +153,6 @@ About dubbo-go benchmarking report, please refer to [dubbo benchmarking report]( If you are using [apache/dubbo-go](github.com/apache/dubbo-go) and think that it helps you or want do some contributions to it, please add your company to to [the user list](https://github.com/apache/dubbo-go/issues/2) to let us know your needs. - - - - -## Stargazers - -[](https://starchart.cc/apache/dubbo-go) - + + + diff --git a/README_CN.md b/README_CN.md index 22af253416017403eaad2579ff977c6925936d7a..ade924e7a9a6206b6e935e084d68679957dd7fcb 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,6 +2,7 @@ [](https://travis-ci.org/apache/dubbo-go) [](https://codecov.io/gh/apache/dubbo-go) +[](https://pkg.go.dev/github.com/apache/dubbo-go?tab=doc) --- Apache Dubbo Go 璇█瀹炵幇 @@ -12,11 +13,13 @@ Apache License, Version 2.0 ## 鍙戝竷鏃ュ織 ## -[v1.0.0 - 2019骞�5鏈�29鏃� 鍏煎dubbo v2.6.5 鐗堟湰](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) +[v1.3.0 - 2020骞�3鏈�1鏃(https://github.com/apache/dubbo-go/releases/tag/v1.3.0) + +[v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0) [v1.1.0 - 2019骞�9鏈�7鏃� 鎹愮尞缁橝pache涔嬪悗鐨勭涓€娆elease](https://github.com/apache/dubbo-go/releases/tag/v1.1.0) -[v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0) +[v1.0.0 - 2019骞�5鏈�29鏃� 鍏煎dubbo v2.6.5 鐗堟湰](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## 宸ョ▼鏋舵瀯 ## @@ -33,7 +36,7 @@ Apache License, Version 2.0 - 瑙掕壊绔� * Consumer * Provider - + - 浼犺緭鍗忚 * HTTP * TCP @@ -46,17 +49,17 @@ Apache License, Version 2.0 * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) - + - 娉ㄥ唽涓績 * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) - + - 鍔ㄦ€侀厤缃腑蹇冧笌鏈嶅姟娌荤悊閰嶇疆鍣� * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) - + - 闆嗙兢绛栫暐 * Failover * [Failfast](https://github.com/apache/dubbo-go/pull/140) @@ -64,12 +67,13 @@ Apache License, Version 2.0 * [Available](https://github.com/apache/dubbo-go/pull/155) * [Broadcast](https://github.com/apache/dubbo-go/pull/158) * [Forking](https://github.com/apache/dubbo-go/pull/161) - + - 璐熻浇鍧囪 绛栫暐 * Random * [RoundRobin](https://github.com/apache/dubbo-go/pull/66) * [LeastActive](https://github.com/apache/dubbo-go/pull/65) - + * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261) + - 杩囨护鍣� * Echo Health Check * [鏈嶅姟鐔旀柇&闄嶇骇](https://github.com/apache/dubbo-go/pull/133) @@ -77,10 +81,10 @@ Apache License, Version 2.0 * [AccessLogFilter](https://github.com/apache/dubbo-go/pull/214) * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) - + - 璋冪敤 * [娉涘寲璋冪敤](https://github.com/apache/dubbo-go/pull/122) - + - 鍏朵粬鍔熻兘鏀寔: * 鍚姩鏃舵鏌� * 鏈嶅姟鐩磋繛 @@ -91,14 +95,13 @@ Apache License, Version 2.0 寮€鍙戜腑鍒楄〃: -- 璐熻浇鍧囪 绛栫暐: ConsistentHash - 娉ㄥ唽涓績: k8s - 鍏冩暟鎹腑蹇� (dubbo v2.7.x) - Metrics: Opentracing/Promethus(dubbo v2.7.x) 浣犲彲浠ラ€氳繃璁块棶 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 鐭ラ亾鏇村鍏充簬 dubbo-go 鐨勪俊鎭€� - + ## 鏂囨。 @@ -122,7 +125,7 @@ Windows before_ut.bat ``` -# 鎵ц +### 鎵ц ```bash go test ./... @@ -130,6 +133,10 @@ go test ./... go test ./... -coverprofile=coverage.txt -covermode=atomic ``` +## 缂栬瘧 + +璇风Щ姝� [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) + ## 濡備綍璐$尞 濡傛灉鎮ㄦ効鎰忕粰 [Apache/dubbo-go](https://github.com/apache/dubbo-go) 璐$尞浠g爜鎴栬€呮枃妗o紝鎴戜滑閮界儹鐑堟杩庛€傚叿浣撹鍙傝€� [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md)銆� @@ -145,8 +152,5 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic 鑻ヤ綘姝e湪浣跨敤 [apache/dubbo-go](github.com/apache/dubbo-go) 涓旇涓哄叾鏈夌敤鎴栬€呭悜瀵瑰叾鍋氭敼杩涳紝璇峰繚鍒楄吹鍙镐俊鎭簬 [鐢ㄦ埛鍒楄〃](https://github.com/apache/dubbo-go/issues/2)锛屼互渚挎垜浠煡鏅撲箣銆�  - -## Stargazers - -[](https://starchart.cc/apache/dubbo-go) - + + diff --git a/before_ut.bat b/before_ut.bat index 5296d0f8769b7b9f521f82e68bf3b10f4b5d16b4..abe7fc250ef44bf01396ae20c4dacc9db3f60ce2 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -14,8 +14,24 @@ :: See the License for the specific language governing permissions and :: limitations under the License. -set zkJar=zookeeper-3.4.9-fatjar.jar -md remoting\zookeeper\zookeeper-4unittest\contrib\fatjar config_center\zookeeper\zookeeper-4unittest\contrib\fatjar registry\zookeeper\zookeeper-4unittest\contrib\fatjar -curl -L https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJar% -o remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar% -xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/" -xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file +set zkJarName="zookeeper-3.4.9-fatjar.jar" +set remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJarName%" +set zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" +set zkJar="%zkJarPath%/%zkJarName%" + +if not exist "%zkJar%" ( + md %zkJarPath% + curl -L %remoteJarUrl% -o %zkJar% +) + +md config_center\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/" + +md registry\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\chain\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\condition\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file diff --git a/before_ut.sh b/before_ut.sh old mode 100644 new mode 100755 index 323173bcc64c3cbe9916747e10dd3ea8538457ea..7ee92e57a26cbdbb1d1a0b3e792726ad5e1954f8 --- a/before_ut.sh +++ b/before_ut.sh @@ -14,8 +14,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +zkJarName="zookeeper-3.4.9-fatjar.jar" +remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${zkJarName}" +zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" +zkJar="${zkJarPath}/${zkJarName}" -mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar -wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ \ No newline at end of file +if [ ! -f "${zkJar}" ]; then + mkdir -p ${zkJarPath} + wget -P "${zkJarPath}" ${remoteJarUrl} +fi + +mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar + +mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index 644f67c5244350897bbc3e291e66e2421758fce5..12799994125c4bf5d968dfc811cda374effbf85c 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -45,6 +45,7 @@ func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { destroyed: atomic.NewBool(false), } } + func (invoker *baseClusterInvoker) GetUrl() common.URL { return invoker.directory.GetUrl() } diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go index 49df78c41b3c3cc7dacf92153fc7e4515a0caec0..d074697b85a3cf5b770de90da4847043d98c9df1 100644 --- a/cluster/cluster_impl/base_cluster_invoker_test.go +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -47,6 +47,7 @@ func Test_StickyNormal(t *testing.T) { result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) assert.Equal(t, result, result1) } + func Test_StickyNormalWhenError(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index 99c584bb583c1d59ece505feafd74ad6e11f6b9a..1be21067a6a9045cb6ae6f84655d516fea1f844b 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -118,6 +118,7 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio } return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } + func Test_FailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} result := normalInvoke(t, 3, urlParams) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index e1a38c4c82cbac61fdfd96cd284c4eea44c97ccc..75d9ef26567df0fbd83f5d9f94c8548d1e8e633d 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -20,39 +20,94 @@ package directory import ( "sync" ) + import ( + "github.com/dubbogo/gost/container/set" "go.uber.org/atomic" ) + import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" ) -// BaseDirectory ... +var routerURLSet = gxset.NewSet() + +// BaseDirectory Abstract implementation of Directory: Invoker list returned from this Directory's list method have been filtered by Routers type BaseDirectory struct { url *common.URL destroyed *atomic.Bool - mutex sync.Mutex + // this mutex for change the properties in BaseDirectory, like routerChain , destroyed etc + mutex sync.Mutex + routerChain router.Chain } -// NewBaseDirectory ... +// NewBaseDirectory Create BaseDirectory with URL func NewBaseDirectory(url *common.URL) BaseDirectory { return BaseDirectory{ - url: url, - destroyed: atomic.NewBool(false), + url: url, + destroyed: atomic.NewBool(false), + routerChain: &chain.RouterChain{}, } } -// GetUrl ... +// RouterChain Return router chain in directory +func (dir *BaseDirectory) RouterChain() router.Chain { + return dir.routerChain +} + +// SetRouterChain Set router chain in directory +func (dir *BaseDirectory) SetRouterChain(routerChain router.Chain) { + dir.mutex.Lock() + defer dir.mutex.Unlock() + dir.routerChain = routerChain +} + +// GetUrl Get URL func (dir *BaseDirectory) GetUrl() common.URL { return *dir.url } -// GetDirectoryUrl ... +// GetDirectoryUrl Get URL instance func (dir *BaseDirectory) GetDirectoryUrl() *common.URL { return dir.url } -// Destroy ... +// SetRouters Convert url to routers and add them into dir.routerChain +func (dir *BaseDirectory) SetRouters(urls []*common.URL) { + if len(urls) == 0 { + return + } + + routers := make([]router.Router, 0, len(urls)) + + for _, url := range urls { + routerKey := url.GetParam(constant.ROUTER_KEY, "") + + if len(routerKey) > 0 { + factory := extension.GetRouterFactory(url.Protocol) + r, err := factory.NewRouter(url) + if err != nil { + logger.Errorf("Create router fail. router key: %s, error: %v", routerKey, url.Service(), err) + return + } + routers = append(routers, r) + } + } + + logger.Infof("Init file condition router success, size: %v", len(routers)) + dir.mutex.Lock() + rc := dir.routerChain + dir.mutex.Unlock() + + rc.AddRouters(routers) +} + +// Destroy Destroy func (dir *BaseDirectory) Destroy(doDestroy func()) { if dir.destroyed.CAS(false, true) { dir.mutex.Lock() @@ -61,7 +116,18 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) { } } -// IsAvailable ... +// IsAvailable Once directory init finish, it will change to true func (dir *BaseDirectory) IsAvailable() bool { return !dir.destroyed.Load() } + +// GetRouterURLSet Return router URL +func GetRouterURLSet() *gxset.HashSet { + return routerURLSet +} + +// AddRouterURLSet Add router URL +// Router URL will init in config/config_loader.go +func AddRouterURLSet(url *common.URL) { + routerURLSet.Add(url) +} diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d5993959f1d37f343a612e2bee305461d49535d0 --- /dev/null +++ b/cluster/directory/base_directory_test.go @@ -0,0 +1,71 @@ +/* + * 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 directory + +import ( + "encoding/base64" + "fmt" + "testing" +) + +import ( + gxnet "github.com/dubbogo/gost/net" + "github.com/stretchr/testify/assert" +) + +import ( + _ "github.com/apache/dubbo-go/cluster/router/condition" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +func TestNewBaseDirectory(t *testing.T) { + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")) + directory := NewBaseDirectory(&url) + + assert.NotNil(t, directory) + + assert.Equal(t, url, directory.GetUrl()) + assert.Equal(t, &url, directory.GetDirectoryUrl()) + +} + +func TestBuildRouterChain(t *testing.T) { + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")) + directory := NewBaseDirectory(&url) + + assert.NotNil(t, directory) + + localIP, _ := gxnet.GetLocalIP() + rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) + routeURL := getRouteUrl(rule) + routerURLs := make([]*common.URL, 0) + routerURLs = append(routerURLs, routeURL) + directory.SetRouters(routerURLs) + chain := directory.RouterChain() + + assert.NotNil(t, chain) +} + +func getRouteUrl(rule string) *common.URL { + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url.AddParam("rule", rule) + url.AddParam("force", "true") + url.AddParam(constant.ROUTER_KEY, "router") + return &url +} diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index 7d2d5490b02d22b12d55385458715fa8b31f2cac..9f600fedc40cf29a40abca6c11652935f20473b4 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -18,6 +18,11 @@ package directory import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) @@ -27,7 +32,7 @@ type staticDirectory struct { invokers []protocol.Invoker } -// NewStaticDirectory ... +// NewStaticDirectory Create a new staticDirectory with invokers func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory { var url common.URL @@ -53,11 +58,21 @@ func (dir *staticDirectory) IsAvailable() bool { return true } +// List List invokers func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invoker { - //TODO:Here should add router - return dir.invokers + l := len(dir.invokers) + invokers := make([]protocol.Invoker, l, l) + copy(invokers, dir.invokers) + routerChain := dir.RouterChain() + + if routerChain == nil { + return invokers + } + dirUrl := dir.GetUrl() + return routerChain.Route(invokers, &dirUrl, invocation) } +// Destroy Destroy func (dir *staticDirectory) Destroy() { dir.BaseDirectory.Destroy(func() { for _, ivk := range dir.invokers { @@ -66,3 +81,17 @@ func (dir *staticDirectory) Destroy() { dir.invokers = []protocol.Invoker{} }) } + +// BuildRouterChain build router chain by invokers +func (dir *staticDirectory) BuildRouterChain(invokers []protocol.Invoker) error { + if len(invokers) == 0 { + return perrors.Errorf("invokers == null") + } + url := invokers[0].GetUrl() + routerChain, e := chain.NewRouterChain(&url) + if e != nil { + return e + } + dir.SetRouterChain(routerChain) + return nil +} diff --git a/cluster/directory/static_directory_test.go b/cluster/directory/static_directory_test.go index 42ef1bcd0b51ce94040b3cbfac37a1f6de686fbb..c50c9a4063bd1a372c27e47687cbf63850f76cef 100644 --- a/cluster/directory/static_directory_test.go +++ b/cluster/directory/static_directory_test.go @@ -40,7 +40,9 @@ func Test_StaticDirList(t *testing.T) { } staticDir := NewStaticDirectory(invokers) - assert.Len(t, staticDir.List(&invocation.RPCInvocation{}), 10) + list := staticDir.List(&invocation.RPCInvocation{}) + + assert.Len(t, list, 10) } func Test_StaticDirDestroy(t *testing.T) { diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go new file mode 100644 index 0000000000000000000000000000000000000000..d48a837eba2080867f370b0cd90e38a0bdf5e417 --- /dev/null +++ b/cluster/router/chain/chain.go @@ -0,0 +1,120 @@ +/* + * 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 chain + +import ( + "math" + "sort" + "sync" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +// RouterChain Router chain +type RouterChain struct { + // Full list of addresses from registry, classified by method name. + invokers []protocol.Invoker + // Containing all routers, reconstruct every time 'route://' urls change. + routers []router.Router + // Fixed router instances: ConfigConditionRouter, TagRouter, e.g., the rule for each instance may change but the + // instance will never delete or recreate. + builtinRouters []router.Router + + mutex sync.RWMutex +} + +// Route Loop routers in RouterChain and call Route method to determine the target invokers list. +func (c *RouterChain) Route(invoker []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + finalInvokers := invoker + l := len(c.routers) + rs := make([]router.Router, l, int(math.Ceil(float64(l)*1.2))) + c.mutex.RLock() + copy(rs, c.routers) + c.mutex.RUnlock() + + for _, r := range rs { + finalInvokers = r.Route(finalInvokers, url, invocation) + } + return finalInvokers +} + +// AddRouters Add routers to router chain +// New a array add builtinRouters which is not sorted in RouterChain and routers +// Sort the array +// Replace router array in RouterChain +func (c *RouterChain) AddRouters(routers []router.Router) { + newRouters := make([]router.Router, 0, len(c.builtinRouters)+len(routers)) + newRouters = append(newRouters, c.builtinRouters...) + newRouters = append(newRouters, routers...) + sortRouter(newRouters) + c.mutex.Lock() + defer c.mutex.Unlock() + c.routers = newRouters +} + +// NewRouterChain Use url to init router chain +// Loop routerFactories and call NewRouter method +func NewRouterChain(url *common.URL) (*RouterChain, error) { + routerFactories := extension.GetRouterFactories() + if len(routerFactories) == 0 { + return nil, perrors.Errorf("No routerFactory exits , create one please") + } + routers := make([]router.Router, 0, len(routerFactories)) + for key, routerFactory := range routerFactories { + r, err := routerFactory().NewRouter(url) + if r == nil || err != nil { + logger.Errorf("router chain build router fail! routerFactories key:%s error:%s", key, err.Error()) + continue + } + routers = append(routers, r) + } + + newRouters := make([]router.Router, len(routers)) + copy(newRouters, routers) + + sortRouter(newRouters) + + chain := &RouterChain{ + builtinRouters: routers, + routers: newRouters, + } + + return chain, nil +} + +// sortRouter Sort router instance by priority with stable algorithm +func sortRouter(routers []router.Router) { + sort.Stable(byPriority(routers)) +} + +// byPriority Sort by priority +type byPriority []router.Router + +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/chain/chain_test.go b/cluster/router/chain/chain_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0cb47c4a185fe19b5f70ea4db2b80aab2f1aada5 --- /dev/null +++ b/cluster/router/chain/chain_test.go @@ -0,0 +1,248 @@ +/* + * 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 chain + +import ( + "encoding/base64" + "fmt" + "strconv" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/condition" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + _ "github.com/apache/dubbo-go/config_center/zookeeper" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting/zookeeper" +) + +func TestNewRouterChain(t *testing.T) { + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + testyml := `enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 +` + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + assert.Nil(t, err) + assert.NotNil(t, configuration) + + chain, err := NewRouterChain(getRouteUrl("test-condition")) + assert.Nil(t, err) + assert.Equal(t, 1, len(chain.routers)) + appRouter := chain.routers[0].(*condition.AppRouter) + + assert.NotNil(t, appRouter) + assert.NotNil(t, appRouter.RouterRule()) + rule := appRouter.RouterRule() + assert.Equal(t, "", rule.Scope) + assert.True(t, rule.Force) + assert.True(t, rule.Enabled) + assert.True(t, rule.Valid) + + assert.Equal(t, testyml, rule.RawRule) + assert.Equal(t, false, rule.Runtime) + assert.Equal(t, false, rule.Dynamic) + assert.Equal(t, "", rule.Key) +} + +func TestNewRouterChainURLNil(t *testing.T) { + chain, err := NewRouterChain(nil) + assert.NoError(t, err) + assert.NotNil(t, chain) +} + +func TestRouterChain_AddRouters(t *testing.T) { + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + testyml := `enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 +` + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + assert.Nil(t, err) + assert.Equal(t, 2, len(chain.routers)) + + url := getConditionRouteUrl("test-condition") + assert.NotNil(t, url) + factory := extension.GetRouterFactory(url.Protocol) + r, err := factory.NewRouter(url) + assert.Nil(t, err) + assert.NotNil(t, r) + + routers := make([]router.Router, 0) + routers = append(routers, r) + chain.AddRouters(routers) + assert.Equal(t, 3, len(chain.routers)) +} + +func TestRouterChain_Route(t *testing.T) { + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + assert.Nil(t, err) + assert.Equal(t, 1, len(chain.routers)) + + url := getConditionRouteUrl("test-condition") + assert.NotNil(t, url) + + invokers := []protocol.Invoker{} + dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService")) + invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + + targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + inv := &invocation.RPCInvocation{} + finalInvokers := chain.Route(invokers, &targetURL, inv) + + assert.Equal(t, 1, len(finalInvokers)) +} + +func TestRouterChain_Route_AppRouter(t *testing.T) { + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + testyml := `enabled: true +force: true +runtime: false +conditions: + - => host = 1.1.1.1 => host != 1.2.3.4 +` + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + assert.Nil(t, err) + assert.Equal(t, 2, len(chain.routers)) + + invokers := []protocol.Invoker{} + dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService")) + invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + + targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + inv := &invocation.RPCInvocation{} + finalInvokers := chain.Route(invokers, &targetURL, inv) + + assert.Equal(t, 0, len(finalInvokers)) +} + +func TestRouterChain_Route_NoRoute(t *testing.T) { + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + chain, err := NewRouterChain(getConditionNoRouteUrl("test-condition")) + assert.Nil(t, err) + assert.Equal(t, 1, len(chain.routers)) + + url := getConditionRouteUrl("test-condition") + assert.NotNil(t, url) + + invokers := []protocol.Invoker{} + dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService")) + invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + + targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + inv := &invocation.RPCInvocation{} + finalInvokers := chain.Route(invokers, &targetURL, inv) + + assert.Equal(t, 0, len(finalInvokers)) +} + +func getConditionNoRouteUrl(applicationKey string) *common.URL { + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url.AddParam("application", applicationKey) + url.AddParam("force", "true") + rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host != 1.2.3.4")) + url.AddParam(constant.RULE_KEY, rule) + return &url +} + +func getConditionRouteUrl(applicationKey string) *common.URL { + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url.AddParam("application", applicationKey) + url.AddParam("force", "true") + rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host = 1.2.3.4")) + url.AddParam(constant.RULE_KEY, rule) + return &url +} + +func getRouteUrl(applicationKey string) *common.URL { + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url.AddParam("application", applicationKey) + url.AddParam("force", "true") + return &url +} diff --git a/cluster/router/condition/app_router.go b/cluster/router/condition/app_router.go new file mode 100644 index 0000000000000000000000000000000000000000..056e32851c11696c80d18a2a55b109fcdae06627 --- /dev/null +++ b/cluster/router/condition/app_router.go @@ -0,0 +1,50 @@ +/* + * 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 ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +const ( + // Default priority for application router + appRouterDefaultPriority = int64(150) +) + +// AppRouter For listen application router with config center +type AppRouter struct { + listenableRouter +} + +// NewAppRouter Init AppRouter by url +func NewAppRouter(url *common.URL) (*AppRouter, error) { + if url == nil { + return nil, perrors.Errorf("No route URL for create app router!") + } + appRouter, err := newListenableRouter(url, url.GetParam(constant.APPLICATION_KEY, "")) + if err != nil { + return nil, err + } + appRouter.priority = appRouterDefaultPriority + return appRouter, nil +} diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bd817af36c8c144295479fb07ada9411f4115bbc --- /dev/null +++ b/cluster/router/condition/app_router_test.go @@ -0,0 +1,178 @@ +/* + * 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 ( + "strconv" + "testing" + "time" +) + +import ( + _ "github.com/apache/dubbo-go/config_center/zookeeper" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/zookeeper" +) + +func TestNewAppRouter(t *testing.T) { + + testYML := `enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 +` + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + assert.Nil(t, err) + assert.NotNil(t, configuration) + + appRouteURL := getAppRouteURL("test-condition") + appRouter, err := NewAppRouter(appRouteURL) + assert.Nil(t, err) + assert.NotNil(t, appRouter) + + assert.NotNil(t, appRouter) + assert.NotNil(t, appRouter.RouterRule()) + rule := appRouter.RouterRule() + assert.Equal(t, "", rule.Scope) + assert.True(t, rule.Force) + assert.True(t, rule.Enabled) + assert.True(t, rule.Valid) + + assert.Equal(t, testYML, rule.RawRule) + assert.Equal(t, false, rule.Runtime) + assert.Equal(t, false, rule.Dynamic) + assert.Equal(t, "", rule.Key) + assert.Equal(t, 0, rule.Priority) +} + +func TestGenerateConditions(t *testing.T) { + + testYML := `enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 + - host = 192.168.199.208 => host = 192.168.199.208 +` + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + assert.Nil(t, err) + assert.NotNil(t, configuration) + + appRouteURL := getAppRouteURL("test-condition") + appRouter, err := NewAppRouter(appRouteURL) + assert.Nil(t, err) + assert.NotNil(t, appRouter) + + rule, err := Parse(testYML) + assert.Nil(t, err) + appRouter.generateConditions(rule) + + assert.Equal(t, 2, len(appRouter.conditionRouters)) +} + +func TestProcess(t *testing.T) { + + testYML := `enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 +` + ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) + err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + assert.NoError(t, err) + + _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + assert.NoError(t, err) + defer ts.Stop() + defer z.Close() + + zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + config.GetEnvInstance().SetDynamicConfiguration(configuration) + + assert.Nil(t, err) + assert.NotNil(t, configuration) + + appRouteURL := getAppRouteURL("test-condition") + appRouter, err := NewAppRouter(appRouteURL) + assert.Nil(t, err) + assert.NotNil(t, appRouter) + + assert.Equal(t, 1, len(appRouter.conditionRouters)) + + testNewYML := ` +enabled: true +force: true +runtime: false +conditions: + - => host != 172.22.3.91 + - host = 192.168.199.208 => host = 192.168.199.208 +` + + appRouter.Process(&config_center.ConfigChangeEvent{ConfigType: remoting.EventTypeDel}) + + assert.Equal(t, 0, len(appRouter.conditionRouters)) + + appRouter.Process(&config_center.ConfigChangeEvent{Value: testNewYML, ConfigType: remoting.EventTypeAdd}) + + assert.Equal(t, 2, len(appRouter.conditionRouters)) +} + +func getAppRouteURL(applicationKey string) *common.URL { + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url.AddParam("application", applicationKey) + url.AddParam("force", "true") + return &url +} diff --git a/cluster/router/condition/factory.go b/cluster/router/condition/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..66512a138706e9b9ab565f7537a15c37a75deefd --- /dev/null +++ b/cluster/router/condition/factory.go @@ -0,0 +1,59 @@ +/* + * 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 ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" +) + +func init() { + extension.SetRouterFactory(constant.ConditionRouterName, newConditionRouterFactory) + extension.SetRouterFactory(constant.ConditionAppRouterName, newAppRouterFactory) +} + +// ConditionRouterFactory Condition router factory +type ConditionRouterFactory struct{} + +func newConditionRouterFactory() router.RouterFactory { + return &ConditionRouterFactory{} +} + +// NewRouter Create ConditionRouterFactory by URL +func (c *ConditionRouterFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewConditionRouter(url) +} + +// NewRouter Create FileRouterFactory by Content +func (c *ConditionRouterFactory) NewFileRouter(content []byte) (router.Router, error) { + return NewFileConditionRouter(content) +} + +// AppRouterFactory Application router factory +type AppRouterFactory struct{} + +func newAppRouterFactory() router.RouterFactory { + return &AppRouterFactory{} +} + +// NewRouter Create AppRouterFactory by URL +func (c *AppRouterFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewAppRouter(url) +} diff --git a/cluster/router/condition_router_test.go b/cluster/router/condition/factory_test.go similarity index 74% rename from cluster/router/condition_router_test.go rename to cluster/router/condition/factory_test.go index fc639b9fc77b3b83d01c663fa02a7185a2d03756..99cec34096a55d3c2a967b63afdf5f6d0a77279a 100644 --- a/cluster/router/condition_router_test.go +++ b/cluster/router/condition/factory_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package router +package condition import ( "context" @@ -119,33 +119,33 @@ func (bi *MockInvoker) Destroy() { func TestRoute_matchWhen(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4")) - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(cUrl, inv) + matchWhen := router.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen) rule1 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router1, _ := NewConditionRouterFactory().Router(getRouteUrl(rule1)) - matchWhen1, _ := router1.(*ConditionRouter).MatchWhen(cUrl, inv) + router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1)) + matchWhen1 := router1.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen1) rule2 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 & host !=1.1.1.1 => host = 1.2.3.4")) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - matchWhen2, _ := router2.(*ConditionRouter).MatchWhen(cUrl, inv) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + matchWhen2 := router2.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, false, matchWhen2) rule3 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.4 & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - matchWhen3, _ := router3.(*ConditionRouter).MatchWhen(cUrl, inv) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + matchWhen3 := router3.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen3) rule4 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router4, _ := NewConditionRouterFactory().Router(getRouteUrl(rule4)) - matchWhen4, _ := router4.(*ConditionRouter).MatchWhen(cUrl, inv) + router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4)) + matchWhen4 := router4.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen4) rule5 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.1 => host = 1.2.3.4")) - router5, _ := NewConditionRouterFactory().Router(getRouteUrl(rule5)) - matchWhen5, _ := router5.(*ConditionRouter).MatchWhen(cUrl, inv) + router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5)) + matchWhen5 := router5.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, false, matchWhen5) rule6 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4")) - router6, _ := NewConditionRouterFactory().Router(getRouteUrl(rule6)) - matchWhen6, _ := router6.(*ConditionRouter).MatchWhen(cUrl, inv) + router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6)) + matchWhen6 := router6.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen6) } @@ -162,19 +162,19 @@ func TestRoute_matchFilter(t *testing.T) { rule4 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.2,10.20.3.3,10.20.3.4")) rule5 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host != 10.20.3.3")) rule6 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " serialization = fastjson")) - router1, _ := NewConditionRouterFactory().Router(getRouteUrl(rule1)) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - router4, _ := NewConditionRouterFactory().Router(getRouteUrl(rule4)) - router5, _ := NewConditionRouterFactory().Router(getRouteUrl(rule5)) - router6, _ := NewConditionRouterFactory().Router(getRouteUrl(rule6)) + router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1)) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4)) + router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5)) + router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6)) cUrl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - fileredInvokers1 := router1.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers2 := router2.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers3 := router3.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers4 := router4.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers5 := router5.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers6 := router6.Route(invokers, cUrl, &invocation.RPCInvocation{}) + fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers4 := router4.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers5 := router5.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers6 := router6.Route(invokers, &cUrl, &invocation.RPCInvocation{}) assert.Equal(t, 1, len(fileredInvokers1)) assert.Equal(t, 0, len(fileredInvokers2)) assert.Equal(t, 0, len(fileredInvokers3)) @@ -187,22 +187,22 @@ func TestRoute_matchFilter(t *testing.T) { func TestRoute_methodRoute(t *testing.T) { inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{})) rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(url, inv) + matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv) assert.Equal(t, true, matchWhen) url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") - matchWhen, _ = router.(*ConditionRouter).MatchWhen(url1, inv) + matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) assert.Equal(t, true, matchWhen) url2, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4")) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - matchWhen, _ = router2.(*ConditionRouter).MatchWhen(url2, inv) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + matchWhen = router2.(*ConditionRouter).MatchWhen(&url2, inv) assert.Equal(t, false, matchWhen) url3, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4")) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - matchWhen, _ = router3.(*ConditionRouter).MatchWhen(url3, inv) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv) assert.Equal(t, true, matchWhen) } @@ -214,8 +214,8 @@ func TestRoute_ReturnFalse(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } @@ -226,19 +226,24 @@ func TestRoute_ReturnEmpty(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } func TestRoute_ReturnAll(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - invokers := []protocol.Invoker{&MockInvoker{}, &MockInvoker{}, &MockInvoker{}} + urlString := "dubbo://" + localIP + "/com.foo.BarService" + dubboURL, _ := common.NewURL(urlString) + mockInvoker1 := NewMockInvoker(dubboURL, 1) + mockInvoker2 := NewMockInvoker(dubboURL, 1) + mockInvoker3 := NewMockInvoker(dubboURL, 1) + invokers := []protocol.Invoker{mockInvoker1, mockInvoker2, mockInvoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } @@ -254,8 +259,8 @@ func TestRoute_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -273,8 +278,8 @@ func TestRoute_Empty_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -292,8 +297,8 @@ func TestRoute_False_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -311,8 +316,8 @@ func TestRoute_Placeholder(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -330,8 +335,8 @@ func TestRoute_NoForce(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithNoForce(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithNoForce(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } @@ -347,7 +352,17 @@ func TestRoute_Force(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithForce(rule, "true")) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithForce(rule, "true")) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } + +func TestNewConditionRouterFactory(t *testing.T) { + factory := newConditionRouterFactory() + assert.NotNil(t, factory) +} + +func TestNewAppRouterFactory(t *testing.T) { + factory := newAppRouterFactory() + assert.NotNil(t, factory) +} diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go new file mode 100644 index 0000000000000000000000000000000000000000..efeec53efc840d93c4b6906adfd19820a57b36fd --- /dev/null +++ b/cluster/router/condition/file.go @@ -0,0 +1,110 @@ +/* + * 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 ( + "encoding/base64" + "net/url" + "strconv" + "strings" + "sync" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +// FileConditionRouter Use for parse config file of condition router +type FileConditionRouter struct { + listenableRouter + parseOnce sync.Once + url common.URL +} + +// NewFileConditionRouter Create file condition router instance with content ( from config file) +func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) { + fileRouter := &FileConditionRouter{} + rule, err := Parse(string(content)) + if err != nil { + return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err)) + } + + if !rule.Valid { + return nil, perrors.Errorf("rule content is not verify for condition router , error:%v", perrors.WithStack(err)) + } + + fileRouter.generateConditions(rule) + + return fileRouter, nil +} + +// URL Return URL in file condition router n +func (f *FileConditionRouter) URL() common.URL { + f.parseOnce.Do(func() { + routerRule := f.routerRule + rule := parseCondition(routerRule.Conditions) + f.url = *common.NewURLWithOptions( + common.WithProtocol(constant.CONDITION_ROUTE_PROTOCOL), + common.WithIp(constant.ANYHOST_VALUE), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.RouterForce, strconv.FormatBool(routerRule.Force)), + common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)), + common.WithParamsValue(constant.RULE_KEY, base64.URLEncoding.EncodeToString([]byte(rule))), + common.WithParamsValue(constant.ROUTER_KEY, constant.CONDITION_ROUTE_PROTOCOL), + common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY)) + }) + return f.url +} + +func parseCondition(conditions []string) string { + var ( + when string + then string + ) + for _, condition := range conditions { + condition = strings.Trim(condition, " ") + if strings.Contains(condition, "=>") { + array := strings.SplitN(condition, "=>", 2) + consumer := strings.Trim(array[0], " ") + provider := strings.Trim(array[1], " ") + if len(consumer) != 0 { + if len(when) != 0 { + when = strings.Join([]string{when, consumer}, " & ") + } else { + when = consumer + } + } + if len(provider) != 0 { + if len(then) != 0 { + then = strings.Join([]string{then, provider}, " & ") + } else { + then = provider + } + } + + } + + } + + return strings.Join([]string{when, then}, " => ") +} diff --git a/cluster/router/condition/file_test.go b/cluster/router/condition/file_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3092b12ff88dcacc9108c7cdd46ba1ac9f74eb2b --- /dev/null +++ b/cluster/router/condition/file_test.go @@ -0,0 +1,58 @@ +/* + * 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" +) + +func TestLoadYmlConfig(t *testing.T) { + router, e := NewFileConditionRouter([]byte(`priority: 1 +force: true +conditions : + - "a => b" + - "c => d"`)) + assert.Nil(t, e) + assert.NotNil(t, router) + assert.Equal(t, router.routerRule.Priority, 1) + assert.Equal(t, router.routerRule.Force, true) + assert.Equal(t, len(router.routerRule.Conditions), 2) +} + +func TestParseCondition(t *testing.T) { + s := make([]string, 2) + s = append(s, "a => b") + s = append(s, "c => d") + condition := parseCondition(s) + assert.Equal(t, "a & c => b & d", condition) +} + +func TestFileRouterURL(t *testing.T) { + router, e := NewFileConditionRouter([]byte(`priority: 1 +force: true +conditions : + - "a => b" + - "c => d"`)) + assert.Nil(t, e) + assert.NotNil(t, router) + assert.Equal(t, "condition://0.0.0.0:?category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D", router.URL().String()) +} diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go new file mode 100644 index 0000000000000000000000000000000000000000..ba2fbb0eb2f482dfde215c1b078ecad60e66bc14 --- /dev/null +++ b/cluster/router/condition/listenable_router.go @@ -0,0 +1,151 @@ +/* + * 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 ( + "fmt" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/remoting" +) + +const ( + // Default priority for listenable router, use the maximum int64 value + listenableRouterDefaultPriority = ^int64(0) +) + +// listenableRouter Abstract router which listens to dynamic configuration +type listenableRouter struct { + conditionRouters []*ConditionRouter + routerRule *RouterRule + url *common.URL + force bool + priority int64 +} + +// RouterRule Get RouterRule instance from listenableRouter +func (l *listenableRouter) RouterRule() *RouterRule { + return l.routerRule +} + +func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { + if ruleKey == "" { + return nil, perrors.Errorf("NewListenableRouter ruleKey is nil, can't create Listenable router") + } + l := &AppRouter{} + + l.url = url + l.priority = listenableRouterDefaultPriority + + routerKey := ruleKey + constant.ConditionRouterRuleSuffix + //add listener + dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() + if dynamicConfiguration == nil { + return nil, perrors.Errorf("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") + } + + dynamicConfiguration.AddListener(routerKey, l) + //get rule + rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) + if len(rule) == 0 || err != nil { + return nil, perrors.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) + } + l.Process(&config_center.ConfigChangeEvent{ + Key: routerKey, + Value: rule, + ConfigType: remoting.EventTypeUpdate}) + + logger.Info("Init app router success") + return l, nil +} + +// Process Process config change event , generate routers and set them to the listenableRouter instance +func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { + logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) + if remoting.EventTypeDel == event.ConfigType { + l.routerRule = nil + if l.conditionRouters != nil { + l.conditionRouters = l.conditionRouters[:0] + } + return + } + content, ok := event.Value.(string) + if !ok { + msg := fmt.Sprintf("Convert event content fail,raw content:[%s] ", event.Value) + logger.Error(msg) + return + } + + routerRule, err := Parse(content) + if err != nil { + logger.Errorf("Parse condition router rule fail,error:[%s] ", err) + return + } + l.generateConditions(routerRule) +} + +func (l *listenableRouter) generateConditions(rule *RouterRule) { + if rule == nil || !rule.Valid { + return + } + l.conditionRouters = make([]*ConditionRouter, 0, len(rule.Conditions)) + l.routerRule = rule + for _, c := range rule.Conditions { + router, e := NewConditionRouterWithRule(c) + if e != nil { + logger.Errorf("Create condition router with rule fail,raw rule:[%s] ", c) + continue + } + router.Force = rule.Force + router.enabled = rule.Enabled + l.conditionRouters = append(l.conditionRouters, router) + } +} + +// Route Determine the target invokers list. +func (l *listenableRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if len(invokers) == 0 || len(l.conditionRouters) == 0 { + return invokers + } + //We will check enabled status inside each router. + for _, r := range l.conditionRouters { + invokers = r.Route(invokers, url, invocation) + } + return invokers +} + +// Priority Return Priority in listenable router +func (l *listenableRouter) Priority() int64 { + return l.priority +} + +// URL Return URL in listenable router +func (l *listenableRouter) URL() common.URL { + return *l.url +} diff --git a/cluster/router/condition_router.go b/cluster/router/condition/router.go similarity index 63% rename from cluster/router/condition_router.go rename to cluster/router/condition/router.go index c38e9718cca6d5e67a874a61225bac7d27d5d352..c5d46444bde921386d14a8be7eb0a89d855f8ece 100644 --- a/cluster/router/condition_router.go +++ b/cluster/router/condition/router.go @@ -15,57 +15,55 @@ * limitations under the License. */ -package router +package condition import ( - "reflect" "regexp" "strings" ) import ( - gxset "github.com/dubbogo/gost/container/set" - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) import ( + matcher "github.com/apache/dubbo-go/cluster/router/match" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/net" ) const ( - //ROUTE_PATTERN route pattern regex - ROUTE_PATTERN = `([&!=,]*)\\s*([^&!=,\\s]+)` - // FORCE ... - FORCE = "force" - // PRIORITY ... - PRIORITY = "priority" + //pattern route pattern regex + pattern = `([&!=,]*)\\s*([^&!=,\\s]+)` ) -//ConditionRouter condition router struct +var ( + routerPatternReg = regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`) +) + +// ConditionRouter Condition router struct type ConditionRouter struct { Pattern string - Url *common.URL - Priority int64 + url *common.URL + priority int64 Force bool + enabled bool WhenCondition map[string]MatchPair ThenCondition map[string]MatchPair } -func newConditionRouter(url *common.URL) (*ConditionRouter, error) { +// NewConditionRouterWithRule Init condition router by raw rule +func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) { var ( whenRule string thenRule string when map[string]MatchPair then map[string]MatchPair ) - rule, err := url.GetParamAndDecoded(constant.RULE_KEY) - if err != nil || len(rule) == 0 { - return nil, perrors.Errorf("Illegal route rule!") - } rule = strings.Replace(rule, "consumer.", "", -1) rule = strings.Replace(rule, "provider.", "", -1) i := strings.Index(rule, "=>") @@ -98,31 +96,61 @@ func newConditionRouter(url *common.URL) (*ConditionRouter, error) { then = t } return &ConditionRouter{ - ROUTE_PATTERN, - url, - url.GetParamInt(PRIORITY, 0), - url.GetParamBool(FORCE, false), - when, - then, + Pattern: pattern, + WhenCondition: when, + ThenCondition: then, }, nil } -// Route -// Router determine the target server list. -func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, invocation protocol.Invocation) []protocol.Invoker { - if len(invokers) == 0 { - return invokers +// NewConditionRouter Init condition router by URL +func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { + if url == nil { + return nil, perrors.Errorf("Illegal route URL!") + } + rule, err := url.GetParamAndDecoded(constant.RULE_KEY) + if err != nil || len(rule) == 0 { + return nil, perrors.Errorf("Illegal route rule!") } - isMatchWhen, err := c.MatchWhen(url, invocation) + + router, err := NewConditionRouterWithRule(rule) if err != nil { + return nil, err + } - var urls []string - for _, invo := range invokers { - urls = append(urls, reflect.TypeOf(invo).String()) - } - logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err) + router.url = url + router.priority = url.GetParamInt(constant.RouterPriority, 0) + router.Force = url.GetParamBool(constant.RouterForce, false) + router.enabled = url.GetParamBool(constant.RouterEnabled, true) + + return router, nil +} + +// Priority Return Priority in condition router +func (c *ConditionRouter) Priority() int64 { + return c.priority +} + +// URL Return URL in condition router +func (c *ConditionRouter) URL() common.URL { + return *c.url +} + +// Enabled Return is condition router is enabled +// true: enabled +// false: disabled +func (c *ConditionRouter) Enabled() bool { + return c.enabled +} + +// Route Determine the target invokers list. +func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if !c.Enabled() { + return invokers + } + if len(invokers) == 0 { return invokers } + isMatchWhen := c.MatchWhen(url, invocation) if !isMatchWhen { return invokers } @@ -130,17 +158,9 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv if len(c.ThenCondition) == 0 { return result } - localIP, _ := gxnet.GetLocalIP() for _, invoker := range invokers { - isMatchThen, err := c.MatchThen(invoker.GetUrl(), url) - if err != nil { - var urls []string - for _, invo := range invokers { - urls = append(urls, reflect.TypeOf(invo).String()) - } - logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err) - return invokers - } + invokerUrl := invoker.GetUrl() + isMatchThen := c.MatchThen(&invokerUrl, url) if isMatchThen { result = append(result, invoker) } @@ -149,6 +169,7 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv return result } else if c.Force { rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) + localIP, _ := gxnet.GetLocalIP() logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) return result } @@ -162,15 +183,10 @@ func parseRule(rule string) (map[string]MatchPair, error) { } var ( - pair MatchPair - startIndex int + pair MatchPair ) values := gxset.NewSet() - reg := regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`) - if indexTuple := reg.FindIndex([]byte(rule)); len(indexTuple) > 0 { - startIndex = indexTuple[0] - } - matches := reg.FindAllSubmatch([]byte(rule), -1) + matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1) for _, groups := range matches { separator := string(groups[1]) content := string(groups[2]) @@ -193,22 +209,26 @@ func parseRule(rule string) (map[string]MatchPair, error) { } case "=": if &pair == nil { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values = pair.Matches values.Add(content) case "!=": if &pair == nil { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values = pair.Mismatches values.Add(content) case ",": if values.Empty() { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values.Add(content) default: + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } @@ -216,23 +236,31 @@ func parseRule(rule string) (map[string]MatchPair, error) { return condition, nil } -//MatchWhen MatchWhen -func (c *ConditionRouter) MatchWhen(url common.URL, invocation protocol.Invocation) (bool, error) { - condition, err := MatchCondition(c.WhenCondition, &url, nil, invocation) - return len(c.WhenCondition) == 0 || condition, err +func getStartIndex(rule string) int { + if indexTuple := routerPatternReg.FindIndex([]byte(rule)); len(indexTuple) > 0 { + return indexTuple[0] + } + return -1 } -//MatchThen MatchThen -func (c *ConditionRouter) MatchThen(url common.URL, param common.URL) (bool, error) { - condition, err := MatchCondition(c.ThenCondition, &url, ¶m, nil) - return len(c.ThenCondition) > 0 && condition, err +// MatchWhen MatchWhen +func (c *ConditionRouter) MatchWhen(url *common.URL, invocation protocol.Invocation) bool { + condition := matchCondition(c.WhenCondition, url, nil, invocation) + return len(c.WhenCondition) == 0 || condition } -//MatchCondition MatchCondition -func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) (bool, error) { +// MatchThen MatchThen +func (c *ConditionRouter) MatchThen(url *common.URL, param *common.URL) bool { + condition := matchCondition(c.ThenCondition, url, param, nil) + return len(c.ThenCondition) > 0 && condition +} + +// MatchCondition MatchCondition +func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) bool { sample := url.ToMap() if sample == nil { - return true, perrors.Errorf("url is not allowed be nil") + // because url.ToMap() may return nil, but it should continue to process make condition + sample = make(map[string]string) } var result bool for key, matchPair := range pairs { @@ -248,22 +276,22 @@ func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.U } if len(sampleValue) > 0 { if !matchPair.isMatch(sampleValue, param) { - return false, nil + return false } result = true } else { if !(matchPair.Matches.Empty()) { - return false, nil + return false } result = true } } - return result, nil + return result } -// MatchPair ... +// MatchPair Match key pair , condition process type MatchPair struct { Matches *gxset.HashSet Mismatches *gxset.HashSet @@ -273,7 +301,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Matches.Empty() && pair.Mismatches.Empty() { for match := range pair.Matches.Items { - if isMatchGlobPattern(match.(string), value, param) { + if matcher.IsMatchGlobalPattern(match.(string), value, param) { return true } } @@ -282,20 +310,21 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Mismatches.Empty() && pair.Matches.Empty() { for mismatch := range pair.Mismatches.Items { - if isMatchGlobPattern(mismatch.(string), value, param) { + if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { return false } } return true } if !pair.Mismatches.Empty() && !pair.Matches.Empty() { + //when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { - if isMatchGlobPattern(mismatch.(string), value, param) { + if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { return false } } for match := range pair.Matches.Items { - if isMatchGlobPattern(match.(string), value, param) { + if matcher.IsMatchGlobalPattern(match.(string), value, param) { return true } } @@ -303,31 +332,3 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { } return false } - -func isMatchGlobPattern(pattern string, value string, param *common.URL) bool { - if param != nil && strings.HasPrefix(pattern, "$") { - pattern = param.GetRawParam(pattern[1:]) - } - if "*" == pattern { - return true - } - if len(pattern) == 0 && len(value) == 0 { - return true - } - if len(pattern) == 0 || len(value) == 0 { - return false - } - i := strings.LastIndex(pattern, "*") - switch i { - case -1: - return value == pattern - case len(pattern) - 1: - return strings.HasPrefix(value, pattern[0:i]) - case 0: - return strings.HasSuffix(value, pattern[:i+1]) - default: - prefix := pattern[0:1] - suffix := pattern[i+1:] - return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix) - } -} diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go new file mode 100644 index 0000000000000000000000000000000000000000..1374cf9de2585f78a27e3de99f356c6900268927 --- /dev/null +++ b/cluster/router/condition/router_rule.go @@ -0,0 +1,59 @@ +/* + * 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 ( + "gopkg.in/yaml.v2" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" +) + +// RouterRule RouterRule config read from config file or config center +type RouterRule struct { + router.BaseRouterRule `yaml:",inline""` + Conditions []string +} + +/* Parse Router raw rule parser + * example : + * scope: application + * runtime: true + * force: false + * conditions: + * - > + * method!=sayHello => + * - > + * ip=127.0.0.1 + * => + * 1.1.1.1 + */ +func Parse(rawRule string) (*RouterRule, error) { + r := &RouterRule{} + err := yaml.Unmarshal([]byte(rawRule), r) + if err != nil { + return r, err + } + r.RawRule = rawRule + if len(r.Conditions) != 0 { + r.Valid = true + } + + return r, nil +} diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5acc7283917a7aa662b60cd90daba89d312db0cd --- /dev/null +++ b/cluster/router/condition/router_rule_test.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 condition + +import ( + "testing" +) +import ( + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + testyml := ` +scope: application +runtime: true +force: false +conditions: + - > + method!=sayHello => + - > + ip=127.0.0.1 + => + 1.1.1.1` + rule, e := Parse(testyml) + + assert.Nil(t, e) + assert.NotNil(t, rule) + assert.Equal(t, 2, len(rule.Conditions)) + assert.Equal(t, "application", rule.Scope) + assert.True(t, rule.Runtime) + assert.Equal(t, false, rule.Force) + assert.Equal(t, testyml, rule.RawRule) + assert.True(t, true, rule.Valid) + assert.Equal(t, false, rule.Enabled) + assert.Equal(t, false, rule.Dynamic) + assert.Equal(t, "", rule.Key) +} diff --git a/cluster/router.go b/cluster/router/health_checker.go similarity index 67% rename from cluster/router.go rename to cluster/router/health_checker.go index 589eb9a2696e5772070a94e8c764c78c8e0ca8a2..d9e3087a272dd500cdd1dc9dc6680d436891f88b 100644 --- a/cluster/router.go +++ b/cluster/router/health_checker.go @@ -15,31 +15,14 @@ * limitations under the License. */ -package cluster +package router import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) -// Extension - Router - -// RouterFactory ... -type RouterFactory interface { - Router(*common.URL) (Router, error) -} - -// Router ... -type Router interface { - Route([]protocol.Invoker, common.URL, protocol.Invocation) []protocol.Invoker -} - -// RouterChain ... -type RouterChain struct { - routers []Router -} - -// NewRouterChain ... -func NewRouterChain(url common.URL) { - +// HealthChecker is used to determine whether the invoker is healthy or not +type HealthChecker interface { + // IsHealthy evaluates the healthy state on the given Invoker + IsHealthy(invoker protocol.Invoker) bool } diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go new file mode 100644 index 0000000000000000000000000000000000000000..a26f86ddac45aa6e999cd4453aa296d0786a02ba --- /dev/null +++ b/cluster/router/healthcheck/default_health_check.go @@ -0,0 +1,117 @@ +/* + * 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 healthcheck + +import ( + "math" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +func init() { + extension.SethealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker) +} + +// DefaultHealthChecker is the default implementation of HealthChecker, which determines the health status of +// the invoker based on the number of successive bad request and the current active request. +type DefaultHealthChecker struct { + // the limit of outstanding request + outStandingRequestConutLimit int32 + // the threshold of successive-failure-request + requestSuccessiveFailureThreshold int32 + // value of circuit-tripped timeout factor + circuitTrippedTimeoutFactor int32 +} + +// IsHealthy evaluates the healthy state on the given Invoker based on the number of successive bad request +// and the current active request +func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool { + urlStatus := protocol.GetURLStatus(invoker.GetUrl()) + if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestConutLimit() { + logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key()) + return false + } + return true +} + +// isCircuitBreakerTripped determine whether the invoker is in the tripped state by the number of successive bad request +func (c *DefaultHealthChecker) isCircuitBreakerTripped(status *protocol.RPCStatus) bool { + circuitBreakerTimeout := c.getCircuitBreakerTimeout(status) + currentTime := protocol.CurrentTimeMillis() + if circuitBreakerTimeout <= 0 { + return false + } + return circuitBreakerTimeout > currentTime +} + +// getCircuitBreakerTimeout get the timestamp recovered from tripped state, the unit is millisecond +func (c *DefaultHealthChecker) getCircuitBreakerTimeout(status *protocol.RPCStatus) int64 { + sleepWindow := c.getCircuitBreakerSleepWindowTime(status) + if sleepWindow <= 0 { + return 0 + } + return status.GetLastRequestFailedTimestamp() + sleepWindow +} + +// getCircuitBreakerSleepWindowTime get the sleep window time of invoker, the unit is millisecond +func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol.RPCStatus) int64 { + + successiveFailureCount := status.GetSuccessiveRequestFailureCount() + diff := successiveFailureCount - c.GetRequestSuccessiveFailureThreshold() + if diff < 0 { + return 0 + } else if diff > constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF { + diff = constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF + } + sleepWindow := (1 << diff) * c.GetCircuitTrippedTimeoutFactor() + if sleepWindow > constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS { + sleepWindow = constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS + } + return int64(sleepWindow) +} + +// GetOutStandingRequestConutLimit return the requestSuccessiveFailureThreshold bound to this DefaultHealthChecker +func (c *DefaultHealthChecker) GetRequestSuccessiveFailureThreshold() int32 { + return c.requestSuccessiveFailureThreshold +} + +// GetOutStandingRequestConutLimit return the circuitTrippedTimeoutFactor bound to this DefaultHealthChecker +func (c *DefaultHealthChecker) GetCircuitTrippedTimeoutFactor() int32 { + return c.circuitTrippedTimeoutFactor +} + +// GetOutStandingRequestConutLimit return the outStandingRequestConutLimit bound to this DefaultHealthChecker +func (c *DefaultHealthChecker) GetOutStandingRequestConutLimit() int32 { + return c.outStandingRequestConutLimit +} + +// NewDefaultHealthChecker constructs a new DefaultHealthChecker based on the url +func NewDefaultHealthChecker(url *common.URL) router.HealthChecker { + return &DefaultHealthChecker{ + outStandingRequestConutLimit: int32(url.GetParamInt(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, math.MaxInt32)), + requestSuccessiveFailureThreshold: int32(url.GetParamInt(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF)), + circuitTrippedTimeoutFactor: int32(url.GetParamInt(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR)), + } +} diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go new file mode 100644 index 0000000000000000000000000000000000000000..74aa3940743a012f907cfe3d8811a618f07ff800 --- /dev/null +++ b/cluster/router/healthcheck/default_health_check_test.go @@ -0,0 +1,157 @@ +/* + * 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 healthcheck + +import ( + "math" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" +) + +func TestDefaultHealthChecker_IsHealthy(t *testing.T) { + + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + invoker := NewMockInvoker(url, 1) + healthy := hc.IsHealthy(invoker) + assert.True(t, healthy) + + url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10") + url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "100") + // fake the outgoing request + for i := 0; i < 11; i++ { + request(url, "test", 0, true, false) + } + hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + healthy = hc.IsHealthy(invoker) + // the outgoing request is more than OUTSTANDING_REQUEST_COUNT_LIMIT, go to unhealthy + assert.False(t, hc.IsHealthy(invoker)) + + // successive failed count is more than constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, go to unhealthy + for i := 0; i < 11; i++ { + request(url, "test", 0, false, false) + } + url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") + url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000") + hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + healthy = hc.IsHealthy(invoker) + assert.False(t, hc.IsHealthy(invoker)) + + // reset successive failed count and go to healthy + request(url, "test", 0, false, true) + healthy = hc.IsHealthy(invoker) + assert.True(t, hc.IsHealthy(invoker)) +} + +func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) { + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + // Increase the number of failed requests + for i := 0; i < 100; i++ { + request(url, "test", 1, false, false) + } + sleepWindowTime := defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url)) + assert.True(t, sleepWindowTime == constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS) + + // Adjust the threshold size to 1000 + url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "1000") + sleepWindowTime = NewDefaultHealthChecker(&url).(*DefaultHealthChecker).getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url)) + assert.True(t, sleepWindowTime == 0) + + url1, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider") + sleepWindowTime = defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url1)) + assert.True(t, sleepWindowTime == 0) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + sleepWindowTime = defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url1)) + assert.True(t, sleepWindowTime > 0 && sleepWindowTime < constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS) +} + +func TestDefaultHealthChecker_getCircuitBreakerTimeout(t *testing.T) { + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + timeout := defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url)) + assert.True(t, timeout == 0) + url1, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider") + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + request(url1, "test", 1, false, false) + timeout = defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url1)) + // timeout must after the current time + assert.True(t, timeout > protocol.CurrentTimeMillis()) + +} + +func TestDefaultHealthChecker_isCircuitBreakerTripped(t *testing.T) { + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + status := protocol.GetURLStatus(url) + tripped := defaultHc.isCircuitBreakerTripped(status) + assert.False(t, tripped) + // Increase the number of failed requests + for i := 0; i < 100; i++ { + request(url, "test", 1, false, false) + } + tripped = defaultHc.isCircuitBreakerTripped(protocol.GetURLStatus(url)) + assert.True(t, tripped) + +} + +func TestNewDefaultHealthChecker(t *testing.T) { + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + assert.NotNil(t, defaultHc) + assert.Equal(t, defaultHc.outStandingRequestConutLimit, int32(math.MaxInt32)) + assert.Equal(t, defaultHc.requestSuccessiveFailureThreshold, int32(constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF)) + + url1, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url1.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10") + url1.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") + nondefaultHc := NewDefaultHealthChecker(&url1).(*DefaultHealthChecker) + assert.NotNil(t, nondefaultHc) + assert.Equal(t, nondefaultHc.outStandingRequestConutLimit, int32(10)) + assert.Equal(t, nondefaultHc.requestSuccessiveFailureThreshold, int32(10)) +} + +func request(url common.URL, method string, elapsed int64, active, succeeded bool) { + protocol.BeginCount(url, method) + if !active { + protocol.EndCount(url, method, elapsed, succeeded) + } +} diff --git a/cluster/router/healthcheck/factory.go b/cluster/router/healthcheck/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..32d84d145ceb2aa05f5a75de352e52d13dd9d6b3 --- /dev/null +++ b/cluster/router/healthcheck/factory.go @@ -0,0 +1,43 @@ +/* + * 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 healthcheck + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" +) + +func init() { + extension.SetRouterFactory(constant.HealthCheckRouterName, newHealthCheckRouteFactory) +} + +// HealthCheckRouteFactory +type HealthCheckRouteFactory struct { +} + +// newHealthCheckRouteFactory construct a new HealthCheckRouteFactory +func newHealthCheckRouteFactory() router.RouterFactory { + return &HealthCheckRouteFactory{} +} + +// NewRouter construct a new NewHealthCheckRouter via url +func (f *HealthCheckRouteFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewHealthCheckRouter(url) +} diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a9d94da7c37f0e0c9640de1386998a85823e80a6 --- /dev/null +++ b/cluster/router/healthcheck/factory_test.go @@ -0,0 +1,65 @@ +/* + * 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 healthcheck + +import ( + "context" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +type MockInvoker struct { + url common.URL +} + +func NewMockInvoker(url common.URL, successCount int) *MockInvoker { + return &MockInvoker{ + url: url, + } +} + +func (bi *MockInvoker) GetUrl() common.URL { + return bi.url +} +func (bi *MockInvoker) IsAvailable() bool { + return true +} + +func (bi *MockInvoker) IsDestroyed() bool { + return true +} + +func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { + return nil +} + +func (bi *MockInvoker) Destroy() { +} + +func TestHealthCheckRouteFactory(t *testing.T) { + factory := newHealthCheckRouteFactory() + assert.NotNil(t, factory) +} diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go new file mode 100644 index 0000000000000000000000000000000000000000..1ddc9ccb173881a87bc5351711326f02ab2da3f6 --- /dev/null +++ b/cluster/router/healthcheck/health_check_route.go @@ -0,0 +1,86 @@ +/* + * 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 healthcheck + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +const ( + HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled" +) + +// HealthCheckRouter provides a health-first routing mechanism through HealthChecker +type HealthCheckRouter struct { + url *common.URL + enabled bool + checker router.HealthChecker +} + +// NewHealthCheckRouter construct an HealthCheckRouter via url +func NewHealthCheckRouter(url *common.URL) (router.Router, error) { + r := &HealthCheckRouter{ + url: url, + enabled: url.GetParamBool(HEALTH_ROUTE_ENABLED_KEY, false), + } + if r.enabled { + checkerName := url.GetParam(constant.HEALTH_CHECKER, constant.DEFAULT_HEALTH_CHECKER) + r.checker = extension.GetHealthChecker(checkerName, url) + } + return r, nil +} + +// Route gets a list of healthy invoker +func (r *HealthCheckRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if !r.enabled { + return invokers + } + healthyInvokers := make([]protocol.Invoker, 0, len(invokers)) + // Add healthy invoker to the list + for _, invoker := range invokers { + if r.checker.IsHealthy(invoker) { + healthyInvokers = append(healthyInvokers, invoker) + } + } + // If all Invoke are considered unhealthy, downgrade to all inovker + if len(healthyInvokers) == 0 { + logger.Warnf(" Now all invokers are unhealthy, so downgraded to all! Service: [%s]", url.ServiceKey()) + return invokers + } + return healthyInvokers +} + +// Priority +func (r *HealthCheckRouter) Priority() int64 { + return 0 +} + +// URL Return URL in router +func (r *HealthCheckRouter) URL() common.URL { + return *r.url +} + +// HealthyChecker returns the HealthChecker bound to this HealthCheckRouter +func (r *HealthCheckRouter) HealthyChecker() router.HealthChecker { + return r.checker +} diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go new file mode 100644 index 0000000000000000000000000000000000000000..759ef93dbeb8d91a82eefd59060afbe8a10a4440 --- /dev/null +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -0,0 +1,135 @@ +/* + * 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 healthcheck + +import ( + "math" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestHealthCheckRouter_Route(t *testing.T) { + defer protocol.CleanAllStatus() + consumerURL, _ := common.NewURL("dubbo://192.168.10.1/com.ikurento.user.UserProvider") + consumerURL.SetParam(HEALTH_ROUTE_ENABLED_KEY, "true") + url1, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url2, _ := common.NewURL("dubbo://192.168.10.11:20000/com.ikurento.user.UserProvider") + url3, _ := common.NewURL("dubbo://192.168.10.12:20000/com.ikurento.user.UserProvider") + hcr, _ := NewHealthCheckRouter(&consumerURL) + + var invokers []protocol.Invoker + invoker1 := NewMockInvoker(url1, 1) + invoker2 := NewMockInvoker(url2, 1) + invoker3 := NewMockInvoker(url3, 1) + invokers = append(invokers, invoker1, invoker2, invoker3) + inv := invocation.NewRPCInvocation("test", nil, nil) + res := hcr.Route(invokers, &consumerURL, inv) + // now all invokers are healthy + assert.True(t, len(res) == len(invokers)) + + for i := 0; i < 10; i++ { + request(url1, "test", 0, false, false) + } + res = hcr.Route(invokers, &consumerURL, inv) + // invokers1 is unhealthy now + assert.True(t, len(res) == 2 && !contains(res, invoker1)) + + for i := 0; i < 10; i++ { + request(url1, "test", 0, false, false) + request(url2, "test", 0, false, false) + } + + res = hcr.Route(invokers, &consumerURL, inv) + // only invokers3 is healthy now + assert.True(t, len(res) == 1 && !contains(res, invoker1) && !contains(res, invoker2)) + + for i := 0; i < 10; i++ { + request(url1, "test", 0, false, false) + request(url2, "test", 0, false, false) + request(url3, "test", 0, false, false) + } + + res = hcr.Route(invokers, &consumerURL, inv) + // now all invokers are unhealthy, so downgraded to all + assert.True(t, len(res) == 3) + + // reset the invoker1 successive failed count, so invoker1 go to healthy + request(url1, "test", 0, false, true) + res = hcr.Route(invokers, &consumerURL, inv) + assert.True(t, contains(res, invoker1)) + + for i := 0; i < 6; i++ { + request(url1, "test", 0, false, false) + } + // now all invokers are unhealthy, so downgraded to all again + res = hcr.Route(invokers, &consumerURL, inv) + assert.True(t, len(res) == 3) + time.Sleep(time.Second * 2) + // invoker1 go to healthy again after 2s + res = hcr.Route(invokers, &consumerURL, inv) + assert.True(t, contains(res, invoker1)) + +} + +func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool { + for _, e := range invokers { + if e == invoker { + return true + } + } + return false +} + +func TestNewHealthCheckRouter(t *testing.T) { + defer protocol.CleanAllStatus() + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + hcr, _ := NewHealthCheckRouter(&url) + h := hcr.(*HealthCheckRouter) + assert.Nil(t, h.checker) + + url.SetParam(HEALTH_ROUTE_ENABLED_KEY, "true") + hcr, _ = NewHealthCheckRouter(&url) + h = hcr.(*HealthCheckRouter) + assert.NotNil(t, h.checker) + + dhc := h.checker.(*DefaultHealthChecker) + assert.Equal(t, dhc.outStandingRequestConutLimit, int32(math.MaxInt32)) + assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(constant.DEFAULT_SUCCESSIVE_FAILED_THRESHOLD)) + assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR)) + + url.SetParam(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, "500") + url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") + url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000") + hcr, _ = NewHealthCheckRouter(&url) + h = hcr.(*HealthCheckRouter) + dhc = h.checker.(*DefaultHealthChecker) + assert.Equal(t, dhc.outStandingRequestConutLimit, int32(1000)) + assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(10)) + assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(500)) +} diff --git a/cluster/router/match/match_utils.go b/cluster/router/match/match_utils.go new file mode 100644 index 0000000000000000000000000000000000000000..28fe7151c5126c41fbadf9f4d54da2b9df74a7fe --- /dev/null +++ b/cluster/router/match/match_utils.go @@ -0,0 +1,63 @@ +/* + * 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 match + +import ( + "strings" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +// IsMatchGlobalPattern Match value to param content by pattern +func IsMatchGlobalPattern(pattern string, value string, param *common.URL) bool { + if param != nil && strings.HasPrefix(pattern, "$") { + pattern = param.GetRawParam(pattern[1:]) + } + return isMatchInternalPattern(pattern, value) +} + +func isMatchInternalPattern(pattern string, value string) bool { + if "*" == pattern { + return true + } + if len(pattern) == 0 && len(value) == 0 { + return true + } + if len(pattern) == 0 || len(value) == 0 { + return false + } + i := strings.LastIndex(pattern, "*") + switch i { + case -1: + // doesn't find "*" + return value == pattern + case len(pattern) - 1: + // "*" is at the end + return strings.HasPrefix(value, pattern[0:i]) + case 0: + // "*" is at the beginning + return strings.HasSuffix(value, pattern[i+1:]) + default: + // "*" is in the middle + prefix := pattern[0:1] + suffix := pattern[i+1:] + return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix) + } +} diff --git a/cluster/router/match/match_utils_test.go b/cluster/router/match/match_utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f16480f1d3b7dd5ca820c81d5d04d837c129687f --- /dev/null +++ b/cluster/router/match/match_utils_test.go @@ -0,0 +1,46 @@ +/* + * 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 match + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +func TestIsMatchInternalPattern(t *testing.T) { + assert.Equal(t, true, isMatchInternalPattern("*", "value")) + assert.Equal(t, true, isMatchInternalPattern("", "")) + assert.Equal(t, false, isMatchInternalPattern("", "value")) + assert.Equal(t, true, isMatchInternalPattern("value", "value")) + assert.Equal(t, true, isMatchInternalPattern("v*", "value")) + assert.Equal(t, true, isMatchInternalPattern("*ue", "value")) + assert.Equal(t, true, isMatchInternalPattern("*e", "value")) + assert.Equal(t, true, isMatchInternalPattern("v*e", "value")) +} + +func TestIsMatchGlobPattern(t *testing.T) { + url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e") + assert.Equal(t, true, IsMatchGlobalPattern("$key", "value", &url)) +} diff --git a/cluster/router/router.go b/cluster/router/router.go new file mode 100644 index 0000000000000000000000000000000000000000..a28002a09e3b7217549b896d452f70997504ac8f --- /dev/null +++ b/cluster/router/router.go @@ -0,0 +1,56 @@ +/* + * 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 router + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +// Extension - Router + +// RouterFactory Router create factory +type RouterFactory interface { + // NewRouter Create router instance with URL + NewRouter(*common.URL) (Router, error) +} + +// RouterFactory Router create factory use for parse config file +type FIleRouterFactory interface { + // NewFileRouters Create file router with config file + NewFileRouter([]byte) (Router, error) +} + +// Router +type Router interface { + // Route Determine the target invokers list. + Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker + // Priority Return Priority in router + // 0 to ^int(0) is better + Priority() int64 + // URL Return URL in router + URL() common.URL +} + +// Chain +type Chain interface { + // Route Determine the target invokers list with chain. + Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker + // AddRouters Add routers + AddRouters([]Router) +} diff --git a/cluster/router/rule.go b/cluster/router/rule.go new file mode 100644 index 0000000000000000000000000000000000000000..42c08a7009a9509749c27e17c465187fe2c85c03 --- /dev/null +++ b/cluster/router/rule.go @@ -0,0 +1,31 @@ +/* + * 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 router + +// BaseRouterRule +type BaseRouterRule struct { + RawRule string + Runtime bool + Force bool + Valid bool + Enabled bool + Priority int + Dynamic bool + Scope string + Key string +} diff --git a/common/constant/env.go b/common/constant/env.go index cb5394bb82ec29d1d24e02627e9d8fafff212efa..5376323328f431083a47395c9e2ebbab5b37f307 100644 --- a/common/constant/env.go +++ b/common/constant/env.go @@ -24,4 +24,6 @@ const ( CONF_PROVIDER_FILE_PATH = "CONF_PROVIDER_FILE_PATH" // APP_LOG_CONF_FILE ... APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" + // CONF_ROUTER_FILE_PATH Specify Path variable of router config file + CONF_ROUTER_FILE_PATH = "CONF_ROUTER_FILE_PATH" ) diff --git a/common/constant/key.go b/common/constant/key.go index 33ce0a38b0be11c17a9cb4e31a44dafd3a1e3ba7..07335bed599788b0240b28b096c7d7a395ee9c11 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -40,6 +40,7 @@ const ( TOKEN_KEY = "token" LOCAL_ADDR = "local-addr" REMOTE_ADDR = "remote-addr" + PATH_SEPARATOR = "/" ) const ( @@ -89,16 +90,23 @@ const ( ) const ( - APPLICATION_KEY = "application" - ORGANIZATION_KEY = "organization" - NAME_KEY = "name" - MODULE_KEY = "module" - APP_VERSION_KEY = "app.version" - OWNER_KEY = "owner" - ENVIRONMENT_KEY = "environment" - METHOD_KEY = "method" - METHOD_KEYS = "methods" - RULE_KEY = "rule" + APPLICATION_KEY = "application" + ORGANIZATION_KEY = "organization" + NAME_KEY = "name" + MODULE_KEY = "module" + APP_VERSION_KEY = "app.version" + OWNER_KEY = "owner" + ENVIRONMENT_KEY = "environment" + METHOD_KEY = "method" + METHOD_KEYS = "methods" + RULE_KEY = "rule" + RUNTIME_KEY = "runtime" + BACKUP_KEY = "backup" + ROUTERS_CATEGORY = "routers" + ROUTE_PROTOCOL = "route" + CONDITION_ROUTE_PROTOCOL = "condition" + PROVIDERS_CATEGORY = "providers" + ROUTER_KEY = "router" ) const ( @@ -120,6 +128,7 @@ const ( ProviderConfigPrefix = "dubbo.provider." ConsumerConfigPrefix = "dubbo.consumer." ShutdownConfigPrefix = "dubbo.shutdown." + RouterConfigPrefix = "dubbo.router." ) const ( @@ -142,6 +151,28 @@ const ( TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx" ) +// Use for router module +const ( + // ConditionRouterName Specify file condition router name + ConditionRouterName = "condition" + // ConditionAppRouterName Specify listenable application router name + ConditionAppRouterName = "app" + // ListenableRouterName Specify listenable router name + ListenableRouterName = "listenable" + // HealthCheckRouterName Specify the name of HealthCheckRouter + HealthCheckRouterName = "health_check" + + // ConditionRouterRuleSuffix Specify condition router suffix + ConditionRouterRuleSuffix = ".condition-router" + + // Force Force key in router module + RouterForce = "force" + // Enabled Enabled key in router module + RouterEnabled = "enabled" + // Priority Priority key in router module + RouterPriority = "priority" +) + const ( // name of consumer sign filter CONSUMER_SIGN_FILTER = "sign" @@ -174,3 +205,25 @@ const ( // key of secret access key SECRET_ACCESS_KEY_KEY = "secretAccessKey" ) + +// HealthCheck Router +const ( + // The key of HealthCheck SPI + HEALTH_CHECKER = "health.checker" + // The name of the default implementation of HealthChecker + DEFAULT_HEALTH_CHECKER = "default" + // The key of oustanding-request-limit + OUTSTANDING_REQUEST_COUNT_LIMIT_KEY = "outstanding.request.limit" + // The key of successive-failed-request's threshold + SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY = "successive.failed.threshold" + // The key of circuit-tripped timeout factor + CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY = "circuit.tripped.timeout.factor" + // The default threshold of successive-failed-request if not specfied + DEFAULT_SUCCESSIVE_FAILED_THRESHOLD = 5 + // The default maximum diff between successive-failed-request's threshold and actual successive-failed-request's count + DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF = 5 + // The default factor of circuit-tripped timeout if not specfied + DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR = 1000 + // The default time window of circuit-tripped in millisecond if not specfied + MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000 +) diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go new file mode 100644 index 0000000000000000000000000000000000000000..365c5d0910812efb00eb94408bb226115b037c02 --- /dev/null +++ b/common/extension/health_checker.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 extension + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" +) + +var ( + healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker) +) + +// SethealthChecker set the HealthChecker with name +func SethealthChecker(name string, fcn func(url *common.URL) router.HealthChecker) { + healthCheckers[name] = fcn +} + +// GetHealthChecker get the HealthChecker with name +func GetHealthChecker(name string, url *common.URL) router.HealthChecker { + if healthCheckers[name] == nil { + panic("healthCheckers for " + name + " is not existing, make sure you have import the package.") + } + return healthCheckers[name](url) +} diff --git a/cluster/router/router_factory.go b/common/extension/health_checker_test.go similarity index 60% rename from cluster/router/router_factory.go rename to common/extension/health_checker_test.go index 723050939e5080f1fefd230986dc679dfbdc06ed..ec934e6e9cedc5acbef350f17b87b0b2e37bc844 100644 --- a/cluster/router/router_factory.go +++ b/common/extension/health_checker_test.go @@ -15,27 +15,35 @@ * limitations under the License. */ -package router +package extension import ( - "github.com/apache/dubbo-go/cluster" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" ) -func init() { - extension.SetRouterFactory("condition", NewConditionRouterFactory) +func TestGetHealthChecker(t *testing.T) { + SethealthChecker("mock", newMockhealthCheck) + checker := GetHealthChecker("mock", common.NewURLWithOptions()) + assert.NotNil(t, checker) } -// ConditionRouterFactory ... -type ConditionRouterFactory struct{} +type mockHealthChecker struct { +} -// NewConditionRouterFactory ... -func NewConditionRouterFactory() cluster.RouterFactory { - return ConditionRouterFactory{} +func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool { + return true } -// Router ... -func (c ConditionRouterFactory) Router(url *common.URL) (cluster.Router, error) { - return newConditionRouter(url) +func newMockhealthCheck(url *common.URL) router.HealthChecker { + return &mockHealthChecker{} } diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go index c77cc291369ab02c5f58dfc6c283902ac0df4b95..70d71dfa859b996030c865775a588da20039f9a5 100644 --- a/common/extension/router_factory.go +++ b/common/extension/router_factory.go @@ -18,23 +18,50 @@ package extension import ( - "github.com/apache/dubbo-go/cluster" + "sync" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" ) var ( - routers = make(map[string]func() cluster.RouterFactory) + routers = make(map[string]func() router.RouterFactory) + fileRouterFactoryOnce sync.Once + fileRouterFactories = make(map[string]router.FIleRouterFactory) ) -// SetRouterFactory ... -func SetRouterFactory(name string, fun func() cluster.RouterFactory) { +// SetRouterFactory Set create router factory function by name +func SetRouterFactory(name string, fun func() router.RouterFactory) { routers[name] = fun } -// GetRouterFactory ... -func GetRouterFactory(name string) cluster.RouterFactory { +// GetRouterFactory Get create router factory function by name +func GetRouterFactory(name string) router.RouterFactory { if routers[name] == nil { panic("router_factory for " + name + " is not existing, make sure you have import the package.") } return routers[name]() +} +// GetRouterFactories Get all create router factory function +func GetRouterFactories() map[string]func() router.RouterFactory { + return routers +} + +// GetFileRouterFactories Get all create file router factory instance +func GetFileRouterFactories() map[string]router.FIleRouterFactory { + l := len(routers) + if l == 0 { + return nil + } + fileRouterFactoryOnce.Do(func() { + for k := range routers { + factory := GetRouterFactory(k) + if fr, ok := factory.(router.FIleRouterFactory); ok { + fileRouterFactories[k] = fr + } + } + }) + return fileRouterFactories } diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 43ca720d0e71577a446829f702c1d2fe23a32905..6765a810a5ed48d95f49b5b97fbf660dd8587715 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -59,6 +59,7 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str // type XxxProvider struct { // Yyy func(ctx context.Context, args []interface{}, rsp *Zzz) error // } + func (p *Proxy) Implement(v common.RPCService) { // check parameters, incoming interface must be a elem's pointer. @@ -142,7 +143,7 @@ func (p *Proxy) Implement(v common.RPCService) { result := p.invoke.Invoke(invCtx, inv) err = result.Error() - logger.Infof("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) + logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) if len(outs) == 1 { return []reflect.Value{reflect.ValueOf(&err).Elem()} } diff --git a/common/url.go b/common/url.go index 360f0aa4caa185d8086eb07497176ae627f47d47..ebb648db27c3efff534f0d0a545f2211f335aa89 100644 --- a/common/url.go +++ b/common/url.go @@ -59,8 +59,8 @@ const ( var ( // DubboNodes ... DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"} - // DubboRole ... - DubboRole = [...]string{"consumer", "", "", "provider"} + // DubboRole Dubbo service role + DubboRole = [...]string{"consumer", "", "routers", "provider"} ) // RoleType ... @@ -240,7 +240,7 @@ func NewURL(urlString string, opts ...option) (URL, error) { if strings.Contains(s.Location, ":") { s.Ip, s.Port, err = net.SplitHostPort(s.Location) if err != nil { - return s, perrors.Errorf("net.SplitHostPort(Url.Host{%s}), error{%v}", s.Location, err) + return s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err) } } for _, opt := range opts { @@ -277,6 +277,7 @@ func (c URL) URLEqual(url URL) bool { } return true } + func isMatchCategory(category1 string, category2 string) bool { if len(category2) == 0 { return category1 == constant.DEFAULT_CATEGORY @@ -288,6 +289,7 @@ func isMatchCategory(category1 string, category2 string) bool { return strings.Contains(category2, category1) } } + func (c URL) String() string { var buildString string if len(c.Username) == 0 && len(c.Password) == 0 { @@ -373,7 +375,7 @@ func (c URL) Service() string { return service } else if c.SubURL != nil { service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) - if service != "" { //if url.path is "" then return suburl's path, special for registry Url + if service != "" { //if url.path is "" then return suburl's path, special for registry url return service } } diff --git a/common/url_test.go b/common/url_test.go index 835973065b6d7426e5487fe76602ca27701130a1..2372de520e88b0949023e88cec64871736dd6aa0 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -164,6 +164,7 @@ func TestURL_GetParamAndDecoded(t *testing.T) { v, _ := u.GetParamAndDecoded("rule") assert.Equal(t, rule, v) } + func TestURL_GetRawParam(t *testing.T) { u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" @@ -176,6 +177,7 @@ func TestURL_GetRawParam(t *testing.T) { assert.Equal(t, "/com.foo.BarService", u.GetRawParam("path")) assert.Equal(t, "fastjson", u.GetRawParam("serialization")) } + func TestURL_ToMap(t *testing.T) { u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" diff --git a/config/base_config.go b/config/base_config.go index 942e966eb31e9570e957ad74aa696aa9ef29c5b0..6d5ec7e2498ba65b2a6833b6c9cefcb3394e60df 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -18,6 +18,8 @@ package config import ( + "io/ioutil" + "path" "reflect" "strconv" "strings" @@ -25,6 +27,7 @@ import ( import ( perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" ) import ( @@ -138,6 +141,7 @@ func getKeyPrefix(val reflect.Value) []string { return retPrefixs } + func getPtrElement(v reflect.Value) reflect.Value { if v.Kind() == reflect.Ptr { v = v.Elem() @@ -147,6 +151,7 @@ func getPtrElement(v reflect.Value) reflect.Value { } return v } + func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryConfiguration) { for i := 0; i < val.NumField(); i++ { if key := val.Type().Field(i).Tag.Get("property"); key != "-" && key != "" { @@ -300,6 +305,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC } } } + func (c *BaseConfig) fresh() { configList := config.GetEnvInstance().Configuration() for element := configList.Front(); element != nil; element = element.Next() { @@ -360,3 +366,25 @@ func initializeStruct(t reflect.Type, v reflect.Value) { } } + +// loadYMLConfig Load yml config byte from file +func loadYMLConfig(confProFile string) ([]byte, error) { + if len(confProFile) == 0 { + return nil, perrors.Errorf("application configure(provider) file name is nil") + } + + if path.Ext(confProFile) != ".yml" { + return nil, perrors.Errorf("application configure file name{%v} suffix must be .yml", confProFile) + } + + return ioutil.ReadFile(confProFile) +} + +// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object +func unmarshalYMLConfig(confProFile string, out interface{}) error { + confFileStream, err := loadYMLConfig(confProFile) + if err != nil { + return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) + } + return yaml.Unmarshal(confFileStream, out) +} diff --git a/config/base_config_test.go b/config/base_config_test.go index d16b2420922ece60ef2135729cd47d5aa73a3760..6973a4a18b5e3a78d9039bc818a5a2a046613783 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -18,6 +18,7 @@ package config import ( "fmt" + "path/filepath" "reflect" "testing" ) @@ -517,3 +518,13 @@ func Test_initializeStruct(t *testing.T) { return consumerConfig.References != nil }) } + +func TestUnmarshalYMLConfig(t *testing.T) { + conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml") + assert.NoError(t, err) + c := &ConsumerConfig{} + assert.NoError(t, unmarshalYMLConfig(conPath, c)) + assert.Equal(t, "default", c.ProxyFactory) + assert.Equal(t, "dubbo.properties", c.ConfigCenterConfig.ConfigFile) + assert.Equal(t, "100ms", c.Connect_Timeout) +} diff --git a/config/condition_router_config.go b/config/condition_router_config.go new file mode 100644 index 0000000000000000000000000000000000000000..a95b2d2b1265a4c069abd8cbc682a9474c15f454 --- /dev/null +++ b/config/condition_router_config.go @@ -0,0 +1,47 @@ +/* + * 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 config + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" +) + +//RouterInit Load config file to init router config +func RouterInit(confRouterFile string) error { + fileRouterFactories := extension.GetFileRouterFactories() + bytes, err := loadYMLConfig(confRouterFile) + if err != nil { + return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err)) + } + for k, factory := range fileRouterFactories { + r, e := factory.NewFileRouter(bytes) + if e == nil { + url := r.URL() + directory.AddRouterURLSet(&url) + return nil + } + logger.Warnf("router config type %s create fail \n", k) + } + return perrors.Errorf("no file router exists for parse %s , implement router.FIleRouterFactory please.", confRouterFile) +} diff --git a/config/condition_router_config_test.go b/config/condition_router_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2f0a38b2fdf59578c77076680c05b3eca5c26a1c --- /dev/null +++ b/config/condition_router_config_test.go @@ -0,0 +1,67 @@ +/* + * 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 config + +import ( + "strings" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/directory" + _ "github.com/apache/dubbo-go/cluster/router/condition" +) + +const testYML = "testdata/router_config.yml" +const errorTestYML = "testdata/router_config_error.yml" + +func TestString(t *testing.T) { + + s := "a1=>a2" + s1 := "=>a2" + s2 := "a1=>" + + n := strings.SplitN(s, "=>", 2) + n1 := strings.SplitN(s1, "=>", 2) + n2 := strings.SplitN(s2, "=>", 2) + + assert.Equal(t, n[0], "a1") + assert.Equal(t, n[1], "a2") + + assert.Equal(t, n1[0], "") + assert.Equal(t, n1[1], "a2") + + assert.Equal(t, n2[0], "a1") + assert.Equal(t, n2[1], "") +} + +func TestRouterInit(t *testing.T) { + errPro := RouterInit(errorTestYML) + assert.Error(t, errPro) + + assert.Equal(t, 0, directory.GetRouterURLSet().Size()) + + errPro = RouterInit(testYML) + assert.NoError(t, errPro) + + assert.Equal(t, 1, directory.GetRouterURLSet().Size()) +} diff --git a/config/config_loader.go b/config/config_loader.go index 875d1f6ddb84434d32296076cd31be96c1385b8a..437f4d7323e66afcf62808b3c8d6bf51cc5bce88 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -36,21 +36,26 @@ var ( metricConfig *MetricConfig applicationConfig *ApplicationConfig maxWait = 3 + confRouterFile string ) // loaded consumer & provider config from xxx.yml, and log config from xxx.xml // Namely: dubbo.consumer.xml & dubbo.provider.xml in java dubbo func init() { var ( - confConFile, confProFile string + confConFile string + confProFile string ) confConFile = os.Getenv(constant.CONF_CONSUMER_FILE_PATH) confProFile = os.Getenv(constant.CONF_PROVIDER_FILE_PATH) + confRouterFile = os.Getenv(constant.CONF_ROUTER_FILE_PATH) + if errCon := ConsumerInit(confConFile); errCon != nil { log.Printf("[consumerInit] %#v", errCon) consumerConfig = nil } + if errPro := ProviderInit(confProFile); errPro != nil { log.Printf("[providerInit] %#v", errPro) providerConfig = nil @@ -73,6 +78,13 @@ func checkApplicationName(config *ApplicationConfig) { // Load Dubbo Init func Load() { + // init router + if confRouterFile != "" { + if errPro := RouterInit(confRouterFile); errPro != nil { + log.Printf("[routerConfig init] %#v", errPro) + } + } + // reference config if consumerConfig == nil { logger.Warnf("consumerConfig is nil!") @@ -100,6 +112,7 @@ func Load() { ref.Refer(rpcService) ref.Implement(rpcService) } + //wait for invoker is available, if wait over default 3s, then panic var count int checkok := true diff --git a/config/protocol_config.go b/config/protocol_config.go index 4828d6e5bd28de19d896340f39c5633d0acd4874..33de976bc6f5bf7341ddcff8d51c505cf23bbccd 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -38,14 +38,13 @@ func (c *ProtocolConfig) Prefix() string { } func loadProtocol(protocolsIds string, protocols map[string]*ProtocolConfig) []*ProtocolConfig { - returnProtocols := []*ProtocolConfig{} + returnProtocols := make([]*ProtocolConfig, 0, len(protocols)) for _, v := range strings.Split(protocolsIds, ",") { - for k, prot := range protocols { + for k, protocol := range protocols { if v == k { - returnProtocols = append(returnProtocols, prot) + returnProtocols = append(returnProtocols, protocol) } } - } return returnProtocols } diff --git a/config/reference_config.go b/config/reference_config.go index edfa17a27e88a605b71bc7f6dec1b133bd29abe9..7ce0013194f5c1a1d09e014a858433833aa07f0e 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -77,7 +77,6 @@ func NewReferenceConfig(id string, ctx context.Context) *ReferenceConfig { // UnmarshalYAML ... func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - type rf ReferenceConfig raw := rf{} // Put your defaults here if err := unmarshal(&raw); err != nil { @@ -101,8 +100,8 @@ func (c *ReferenceConfig) Refer(_ interface{}) { common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), ) - //1. user specified URL, could be peer-to-peer address, or register center's address. if c.Url != "" { + // 1. user specified URL, could be peer-to-peer address, or register center's address. urlStrings := gxstrings.RegSplit(c.Url, "\\s*[;]+\\s*") for _, urlStr := range urlStrings { serviceUrl, err := common.NewURL(urlStr) @@ -120,21 +119,21 @@ func (c *ReferenceConfig) Refer(_ interface{}) { newUrl := common.MergeUrl(&serviceUrl, cfgURL) c.urls = append(c.urls, newUrl) } - } } else { - //2. assemble SubURL from register center's configuration妯″紡 + // 2. assemble SubURL from register center's configuration mode c.urls = loadRegistries(c.Registry, consumerConfig.Registries, common.CONSUMER) - //set url to regUrls + // set url to regUrls for _, regUrl := range c.urls { regUrl.SubURL = cfgURL } } + if len(c.urls) == 1 { c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(*c.urls[0]) } else { - invokers := []protocol.Invoker{} + invokers := make([]protocol.Invoker, 0, len(c.urls)) var regUrl *common.URL for _, u := range c.urls { invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(*u)) @@ -151,7 +150,7 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } } - //create proxy + // create proxy if c.Async { callback := GetCallback(c.id) c.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetAsyncProxy(c.invoker, callback, cfgURL) @@ -219,7 +218,6 @@ func (c *ReferenceConfig) getUrlMap() url.Values { } return urlMap - } // GenericLoad ... diff --git a/config/registry_config_test.go b/config/registry_config_test.go index 45d38b29cc7089dabc5d7b7e34390ee48a58dc97..6c2fed605d6c50b483f7ad2900e5a483b3986e1b 100644 --- a/config/registry_config_test.go +++ b/config/registry_config_test.go @@ -46,6 +46,7 @@ func Test_loadRegistries(t *testing.T) { fmt.Println(urls[0]) assert.Equal(t, "127.0.0.2:2181,128.0.0.1:2181", urls[0].Location) } + func Test_loadRegistries1(t *testing.T) { target := "shanghai1" regs := map[string]*RegistryConfig{ diff --git a/config/service_config.go b/config/service_config.go index 2111838395d507ebac4f72883c99dd2bb1615850..7d97fa4d1e95bd79e051f77deaeafa1afcc58b0f 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -96,14 +96,12 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { // NewServiceConfig The only way to get a new ServiceConfig func NewServiceConfig(id string, context context.Context) *ServiceConfig { - return &ServiceConfig{ context: context, id: id, unexported: atomic.NewBool(false), exported: atomic.NewBool(false), } - } // Export ... @@ -171,10 +169,8 @@ func (c *ServiceConfig) Export() error { panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) } } - } return nil - } // Implement ... @@ -242,5 +238,4 @@ func (c *ServiceConfig) getUrlMap() url.Values { } return urlMap - } diff --git a/config/testdata/router_config.yml b/config/testdata/router_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..f6b91f5da7d95256e6279924b884bfd450c45a08 --- /dev/null +++ b/config/testdata/router_config.yml @@ -0,0 +1,6 @@ +# dubbo router yaml configure file +priority: 1 +force: true +conditions : + - "a => b" + - "c => d" \ No newline at end of file diff --git a/config/testdata/router_config_error.yml b/config/testdata/router_config_error.yml new file mode 100644 index 0000000000000000000000000000000000000000..37894ac96474281d10131b53d8e644f10a18b14e --- /dev/null +++ b/config/testdata/router_config_error.yml @@ -0,0 +1,6 @@ +# dubbo router yaml configure file +priority: 1 +force: true +noConditions : + - "a => b" + - "c => d" \ No newline at end of file diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 85dff14a1ec9ba3905890bf37dc1e1827d59d80f..4dc19817846fe5c9c0552738f2058a15d20efabc 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -163,6 +163,7 @@ func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) stri func (c *apolloConfiguration) Parser() parser.ConfigurationParser { return c.parser } + func (c *apolloConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index d0b23ef2f20d065135547536c2cebcec3eec0ce1..18415bee3a28b37ffc2f3f73cc7309b685de5408 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -36,6 +36,7 @@ import ( func init() { extension.SetDefaultConfigurator(newConfigurator) } + func newConfigurator(url *common.URL) config_center.Configurator { return &overrideConfigurator{configuratorUrl: url} } diff --git a/config_center/configurator/override_test.go b/config_center/configurator/override_test.go index 329c598efe8ef79d7fc1b79ae182c59b238283ac..c0aeb15130e7862fcb00d6cb82cbef60df777acb 100644 --- a/config_center/configurator/override_test.go +++ b/config_center/configurator/override_test.go @@ -40,8 +40,8 @@ func Test_configureVerison2p6(t *testing.T) { assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, "")) - } + func Test_configureVerisonOverrideAddr(t *testing.T) { url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&providerAddresses=127.0.0.2:20001|127.0.0.3:20001") assert.NoError(t, err) @@ -52,8 +52,8 @@ func Test_configureVerisonOverrideAddr(t *testing.T) { assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failover", providerUrl.GetParam(constant.CLUSTER_KEY, "")) - } + func Test_configureVerison2p6WithIp(t *testing.T) { url, err := common.NewURL("override://127.0.0.1:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") assert.NoError(t, err) diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go index 90cd3bbb1d502a0e9ceb8fed5c94a4091bc0578e..d6c3b06b327f16c709b09121e589db6694d3663e 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -22,6 +22,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/config_center/parser" ) @@ -73,3 +74,8 @@ func WithTimeout(time time.Duration) Option { opt.Timeout = time } } + +//GetRuleKey The format is '{interfaceName}:[version]:[group]' +func GetRuleKey(url common.URL) string { + return url.ColonSeparatedKey() +} diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index 06db101d888701d73c9d2bdf87c3715a73c4ee46..d3373e249bf99873dd3aa05b7488b0e7f38730ec 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -1,3 +1,20 @@ +/* + * 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 nacos import ( diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go index 96b4c9d4ac7af8d48e570f0b702b7529cc5603dd..ef63eeff6ddf4e5cd6fa2ba7da7996b3dbed94ac 100644 --- a/config_center/nacos/client_test.go +++ b/config_center/nacos/client_test.go @@ -1,3 +1,20 @@ +/* + * 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 nacos import ( diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go index 07bf8638d2d80123545db02c16544001c06e335b..b4e6f1d0259979eba28dd81e8f480ab4ae03a39f 100644 --- a/config_center/nacos/impl_test.go +++ b/config_center/nacos/impl_test.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package nacos import ( diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index 58fcdb49dad2c53270894a65bf4ebd9595dc420e..f342dc62e765f8d38c9e64ba3be03f3362f0bf61 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -109,6 +109,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common } return allUrls, nil } + func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -154,6 +155,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR } return urls, nil } + func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -246,6 +248,7 @@ func getParamString(item ConfigItem) (string, error) { return retStr, nil } + func getEnabledString(item ConfigItem, config ConfiguratorConfig) string { retStr := "&enabled=" if len(item.Type) == 0 || item.Type == GeneralType { diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index 70fb196a1eedb994eae38576de35d36deb450aaa..404243d4751146d1edc9a61d51cbb81d73c2ffb1 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -155,6 +155,7 @@ func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_cente func (c *zookeeperDynamicConfiguration) Parser() parser.ConfigurationParser { return c.parser } + func (c *zookeeperDynamicConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go index 1d62f3df86f5706823cab7c9ed0bc1a7d9b380f3..22e15193cba1b533a2b1b965a44bf9665a6a4e5e 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -77,6 +77,7 @@ func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicC return ts, reg } + func Test_GetConfig(t *testing.T) { ts, reg := initZkData("dubbo", t) defer ts.Stop() diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8f19957b2a8639470e5c59a676a22762cc9778 Binary files /dev/null and b/doc/pic/arch/dubbo-go-arch.png differ diff --git a/filter/access_key.go b/filter/access_key.go index c9bdd4ff8993d51e4d5002a1216225e2da074df5..40d4157b31d13ed8fd8b1ba8cc9d16b53638ac6a 100644 --- a/filter/access_key.go +++ b/filter/access_key.go @@ -1,3 +1,20 @@ +/* + * 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 filter import ( diff --git a/filter/authenticator.go b/filter/authenticator.go index ce0547b36b03b7078784a6c05c08cd3f89611ca4..ac2c8601d4a0d2e5ae3aed56415d9d23856cb502 100644 --- a/filter/authenticator.go +++ b/filter/authenticator.go @@ -1,3 +1,20 @@ +/* + * 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 filter import ( diff --git a/filter/filter_impl/auth/accesskey_storage.go b/filter/filter_impl/auth/accesskey_storage.go index 0a2bf47cbd377899ba8a0edf4a67026dd827d41f..5adb9d9ee37329228d1d02dc8802deeede68d327 100644 --- a/filter/filter_impl/auth/accesskey_storage.go +++ b/filter/filter_impl/auth/accesskey_storage.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/accesskey_storage_test.go b/filter/filter_impl/auth/accesskey_storage_test.go index 6ab861a8673b191be0a8063980e1dc53e4e70f60..aa566b81761d1f51a5b9141f8f10e66844f272bb 100644 --- a/filter/filter_impl/auth/accesskey_storage_test.go +++ b/filter/filter_impl/auth/accesskey_storage_test.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go index be86b5c74bb9fd02b96483edb18571d47d205ee7..062744771acf8ccd505265875a103d24afeb06af 100644 --- a/filter/filter_impl/auth/consumer_sign.go +++ b/filter/filter_impl/auth/consumer_sign.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( @@ -38,6 +55,7 @@ func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invo func (csf *ConsumerSignFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } + func getConsumerSignFilter() filter.Filter { return &ConsumerSignFilter{} } diff --git a/filter/filter_impl/auth/consumer_sign_test.go b/filter/filter_impl/auth/consumer_sign_test.go index 9a90970b898b75f3c3f1b195062538e62505a082..b02380e28f51356efae385a2e20a6b1ee4e9aa5c 100644 --- a/filter/filter_impl/auth/consumer_sign_test.go +++ b/filter/filter_impl/auth/consumer_sign_test.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go index 73eb9cddc0e1b7b4747da4b0f3e883075e349226..2b8d55927807407f350ecc6cfc28b6913a6d1a81 100644 --- a/filter/filter_impl/auth/default_authenticator.go +++ b/filter/filter_impl/auth/default_authenticator.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go index 72220eec99533b73cc2c9159e3443d6f566471fa..5b107b5960ff5adc383d52aa5e393d9fc6e71d14 100644 --- a/filter/filter_impl/auth/default_authenticator_test.go +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go index 90804934f6b01a61f021f61f4ee549d744ccee72..0d5772e5508894111a88443bfe2d1b02ebfac54a 100644 --- a/filter/filter_impl/auth/provider_auth.go +++ b/filter/filter_impl/auth/provider_auth.go @@ -1,7 +1,27 @@ +/* + * 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 auth import ( "context" +) + +import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" @@ -38,6 +58,7 @@ func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invo func (paf *ProviderAuthFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } + func getProviderAuthFilter() filter.Filter { return &ProviderAuthFilter{} } diff --git a/filter/filter_impl/auth/provider_auth_test.go b/filter/filter_impl/auth/provider_auth_test.go index 88d6423458d5534f18da4316ffe1bca0b374e43c..626782ae8390f046f441c1f162700a883e6f22d0 100644 --- a/filter/filter_impl/auth/provider_auth_test.go +++ b/filter/filter_impl/auth/provider_auth_test.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go index 60698439c5abc1ff0cc555b2ceec77bf2e0e53d5..043a549a849dde66712e1bef389dd91a024660df 100644 --- a/filter/filter_impl/auth/sign_util.go +++ b/filter/filter_impl/auth/sign_util.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/auth/sign_util_test.go b/filter/filter_impl/auth/sign_util_test.go index de6154e8854af99f8e862d94ee45aefcbf26b12b..a4aaf2da27a8dd14969e0e3f93eaee16dfc31b03 100644 --- a/filter/filter_impl/auth/sign_util_test.go +++ b/filter/filter_impl/auth/sign_util_test.go @@ -1,3 +1,20 @@ +/* + * 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 auth import ( diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index e8ff2679b0294d6519aecd1cc1fe37bdeab89e46..9bc131ef8903942b84df2b8fc14fd11143d1a7b6 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -83,6 +83,7 @@ func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ func GetGenericFilter() filter.Filter { return &GenericFilter{} } + func struct2MapAll(obj interface{}) interface{} { if obj == nil { return obj @@ -127,6 +128,7 @@ func struct2MapAll(obj interface{}) interface{} { return obj } } + func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) { result = m if tagName := structField.Tag.Get("m"); tagName == "" { @@ -136,6 +138,7 @@ func setInMap(m map[string]interface{}, structField reflect.StructField, value i } return } + func headerAtoa(a string) (b string) { b = strings.ToLower(a[:1]) + a[1:] return diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go index 22948353fc16a99696a85489ce5df7dc9b18a7ba..b08229199898a30657682d47c32689dc084f5bf4 100644 --- a/filter/filter_impl/generic_filter_test.go +++ b/filter/filter_impl/generic_filter_test.go @@ -88,6 +88,7 @@ func Test_struct2MapAll_Slice(t *testing.T) { assert.Equal(t, reflect.Slice, reflect.TypeOf(m["caCa"]).Kind()) assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind()) } + func Test_struct2MapAll_Map(t *testing.T) { var testData struct { AaAa string diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go index 66c17d920c14e23f1562773c152e99955a48bfb9..71fc097c8bf4752e0cb2b451b0da7e16480b0701 100644 --- a/filter/filter_impl/hystrix_filter_test.go +++ b/filter/filter_impl/hystrix_filter_test.go @@ -213,6 +213,7 @@ func TestGetHystrixFilterConsumer(t *testing.T) { assert.NotNil(t, get) assert.True(t, get.(*HystrixFilter).COrP) } + func TestGetHystrixFilterProvider(t *testing.T) { get := GetHystrixFilterProvider() assert.NotNil(t, get) diff --git a/go.mod b/go.mod index 0eeef0e096609a6157efa3a8ab9c69607bde9a29..73046bc1866176d5e6b52b7bbdea70851fe32269 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect - github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac + github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible @@ -12,7 +12,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 - github.com/dubbogo/getty v1.3.2 + github.com/dubbogo/getty v1.3.3 github.com/dubbogo/go-zookeeper v1.0.0 github.com/dubbogo/gost v1.5.2 github.com/emicklei/go-restful/v3 v3.0.0 @@ -44,7 +44,7 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 github.com/tebeka/strftime v0.1.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect diff --git a/go.sum b/go.sum index 31cf688f1aa7aa838df8883dd398735f1b0ff8a3..d3570af4839139dc884ff730da333f6fdbe97651 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac h1:QKRMidg/RbdI5oaQWMb8Lxo63S+fLmsgMxsFoOCftKw= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= +github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 h1:1HM47ILUkLaMxLKUub+WHPncqrJGEQ0KRJzSJueMDpY= +github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -102,8 +102,8 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.2 h1:l1KVSs/1CtTKbIPTrkTtBT6S9ddvmswDGoAnnl2CDpM= -github.com/dubbogo/getty v1.3.2/go.mod h1:ANbVQ9tbpZ2b0xdR8nRrgS/oXIsZAeRxzvPSOn/7mbk= +github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= +github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.5.1 h1:oG5dzaWf1KYynBaBoUIOkgT+YD0niHV6xxI0Odq7hDg= @@ -452,6 +452,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= @@ -494,7 +496,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 0765a330b534b4f88a955d1ab898780e9fa60713..5ec7db51af0ddfa6e49d3c65910355f0bf2de414 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -18,6 +18,7 @@ package dubbo import ( + "math/rand" "strings" "sync" "time" @@ -83,6 +84,8 @@ func init() { return } setClientGrpool() + + rand.Seed(time.Now().UnixNano()) } // SetClientConf ... @@ -147,11 +150,18 @@ func NewClient(opt Options) *Client { opt.RequestTimeout = 3 * time.Second } + // make sure that client request sequence is an odd number + initSequence := uint64(rand.Int63n(time.Now().UnixNano())) + if initSequence%2 == 0 { + initSequence++ + } + c := &Client{ opts: opt, pendingResponses: new(sync.Map), conf: *clientConf, } + c.sequence.Store(initSequence) c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) return c diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 3e50eb901dbe1e549aea4ea7414d9617851b5363..76416b2baf1e1db516c00d92ecb8ad618bf186bd 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -83,7 +83,13 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { // Unmarshal ... func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { - codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, buf.Len())) + // fix issue https://github.com/apache/dubbo-go/issues/380 + bufLen := buf.Len() + if bufLen < hessian.HEADER_LENGTH { + return perrors.WithStack(hessian.ErrHeaderNotEnough) + } + + codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) // read header err := codec.ReadHeader(&p.Header) diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/codec_test.go index 149644a6eadc23a72b672ae9fa653a1991802dc0..5dc71f0d080c8c862d68029c7983a4407913307e 100644 --- a/protocol/dubbo/codec_test.go +++ b/protocol/dubbo/codec_test.go @@ -18,12 +18,14 @@ package dubbo import ( + "bytes" "testing" "time" ) import ( hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -72,3 +74,10 @@ func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) { assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5]) assert.Equal(t, map[string]string{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) } + +func TestIssue380(t *testing.T) { + pkg := &DubboPackage{} + buf := bytes.NewBuffer([]byte("hello")) + err := pkg.Unmarshal(buf) + assert.True(t, perrors.Cause(err) == hessian.ErrHeaderNotEnough) +} diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index ef5016e22e7600449a9ace739f06562bae192db0..09c3725710d2a0b821d8e641b0cb7b367189c244 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -128,7 +128,7 @@ func (di *DubboInvoker) Destroy() { for { if di.reqNum == 0 { di.reqNum = -1 - logger.Info("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key()) + logger.Infof("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key()) di.BaseInvoker.Destroy() if di.client != nil { di.client.Close() diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 430c4e49d81d4d5d151a545e1a130fd4ac3fbdc5..0251b78a2b0d27a68461c16c284b1af53bcb08aa 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -124,6 +124,7 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { pendingResponse := h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) if pendingResponse == nil { + logger.Errorf("failed to get pending response context for response package %s", *p) return } diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index 9d381585b56ec439f8ebb8938109baf47bb502b2..918514c2676cfc69336a9f53e6d16d7f23cf7dca 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -78,7 +78,7 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge } time.Sleep(1e6) } - logger.Infof("client init ok") + logger.Debug("client init ok") c.updateActive(time.Now().Unix()) return c, nil @@ -319,9 +319,10 @@ func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPC conn, err := p.get() if err == nil && conn == nil { // create new conn - return newGettyRPCClientConn(p, protocol, addr) + rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr) + return rpcClientConn, perrors.WithStack(err) } - return conn, err + return conn, perrors.WithStack(err) } func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) { diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto index d68e1dd37b3276760623d214662854e931bbdd09..e73f72b1e06c90bd917e905f992efbddd744b4ad 100644 --- a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto +++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto @@ -1,16 +1,19 @@ -// Copyright 2015 The gRPC Authors -// -// Licensed 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. +/* +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. +*/ syntax = "proto3"; option java_multiple_files = true; diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 639fd559aa16689a249d035895fc037dc3bc3f8b..13be47c98ece1cc006250ad49ab2b9a8c3b1f625 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -153,7 +153,7 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { } atomic.StoreInt32(&rpcStatus.successiveRequestFailureCount, 0) } else { - atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, time.Now().Unix()) + atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, CurrentTimeMillis()) atomic.AddInt32(&rpcStatus.successiveRequestFailureCount, 1) atomic.AddInt32(&rpcStatus.failed, 1) atomic.AddInt64(&rpcStatus.failedElapsed, elapsed) @@ -167,3 +167,17 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { func CurrentTimeMillis() int64 { return time.Now().UnixNano() / int64(time.Millisecond) } + +// Destroy is used to clean all status +func CleanAllStatus() { + delete1 := func(key interface{}, value interface{}) bool { + methodStatistics.Delete(key) + return true + } + methodStatistics.Range(delete1) + delete2 := func(key interface{}, value interface{}) bool { + serviceStatistic.Delete(key) + return true + } + serviceStatistic.Range(delete2) +} diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go index ffdb3b535667f32e96c3af2be84851655abf5954..5a07f44eab0db60ba65a155d6c6190ab4ce2d716 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -14,7 +14,7 @@ import ( ) func TestBeginCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") BeginCount(url, "test") @@ -28,7 +28,7 @@ func TestBeginCount(t *testing.T) { } func TestEndCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") EndCount(url, "test", 100, true) @@ -41,7 +41,7 @@ func TestEndCount(t *testing.T) { } func TestGetMethodStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetMethodStatus(url, "test") @@ -50,7 +50,7 @@ func TestGetMethodStatus(t *testing.T) { } func TestGetUrlStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) @@ -59,7 +59,7 @@ func TestGetUrlStatus(t *testing.T) { } func Test_beginCount0(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) @@ -68,7 +68,7 @@ func Test_beginCount0(t *testing.T) { } func Test_All(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") request(url, "test", 100, false, true) @@ -129,23 +129,10 @@ func request(url common.URL, method string, elapsed int64, active, succeeded boo } func TestCurrentTimeMillis(t *testing.T) { - defer destroy() + defer CleanAllStatus() c := CurrentTimeMillis() assert.NotNil(t, c) str := strconv.FormatInt(c, 10) i, _ := strconv.ParseInt(str, 10, 64) assert.Equal(t, c, i) } - -func destroy() { - delete1 := func(key interface{}, value interface{}) bool { - methodStatistics.Delete(key) - return true - } - methodStatistics.Range(delete1) - delete2 := func(key interface{}, value interface{}) bool { - serviceStatistic.Delete(key) - return true - } - serviceStatistic.Range(delete2) -} diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 42d03e40bef4f078d9fd8e746119523d3d0725b2..a6d2cdf49b0935b2402e03208d1ff5f702e1cc52 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -24,6 +24,7 @@ import ( import ( perrors "github.com/pkg/errors" + "go.uber.org/atomic" ) import ( @@ -61,6 +62,8 @@ type registryDirectory struct { consumerConfigurationListener *consumerConfigurationListener referenceConfigurationListener *referenceConfigurationListener Options + serviceKey string + forbidden atomic.Bool } // NewRegistryDirectory ... @@ -124,14 +127,23 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { } else if url.Protocol == constant.ROUTER_PROTOCOL || //2.for router url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { url = nil - //TODO: router } switch res.Action { case remoting.EventTypeAdd, remoting.EventTypeUpdate: + logger.Infof("selector add service url{%s}", res.Service) + var urls []*common.URL + + for _, v := range directory.GetRouterURLSet().Values() { + urls = append(urls, v.(*common.URL)) + } + + if len(urls) > 0 { + dir.SetRouters(urls) + } + //dir.cacheService.EventTypeAdd(res.Path, dir.serviceTTL) oldInvoker = dir.cacheInvoker(url) case remoting.EventTypeDel: - //dir.cacheService.EventTypeDel(res.Path, dir.serviceTTL) oldInvoker = dir.uncacheInvoker(url) logger.Infof("selector delete service url{%s}", res.Service) default: @@ -179,6 +191,7 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { for _, invokers := range groupInvokersMap { staticDir := directory.NewStaticDirectory(invokers) cluster := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) + staticDir.BuildRouterChain(invokers) groupInvokersList = append(groupInvokersList, cluster.Join(staticDir)) } } @@ -215,13 +228,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { newUrl := common.MergeUrl(url, referenceUrl) dir.overrideUrl(newUrl) if cacheInvoker, ok := dir.cacheInvokersMap.Load(newUrl.Key()); !ok { - logger.Infof("service will be added in cache invokers: invokers url is %s!", newUrl) + logger.Debugf("service will be added in cache invokers: invokers url is %s!", newUrl) newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) if newInvoker != nil { dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) } } else { - logger.Infof("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) + logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) if newInvoker != nil { dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) @@ -234,8 +247,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { //select the protocol invokers from the directory func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker { - //TODO:router - return dir.cacheInvokers + invokers := dir.cacheInvokers + routerChain := dir.RouterChain() + + if routerChain == nil { + return invokers + } + return routerChain.Route(invokers, dir.cacheOriginUrl, invocation) } func (dir *registryDirectory) IsAvailable() bool { diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index 8ebd130d7d1797a9d8707628d7e5920be758e389..0dde44e73c18f65f262e01f499e198995907dece 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -30,6 +30,8 @@ import ( import ( "github.com/apache/dubbo-go/cluster/cluster_impl" + _ "github.com/apache/dubbo-go/cluster/router" + _ "github.com/apache/dubbo-go/cluster/router/condition" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -43,6 +45,7 @@ import ( func init() { config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) } + func TestSubscribe(t *testing.T) { registryDirectory, _ := normalRegistryDir() @@ -104,7 +107,7 @@ func TestSubscribe_Group(t *testing.T) { func Test_Destroy(t *testing.T) { registryDirectory, _ := normalRegistryDir() - time.Sleep(1e9) + time.Sleep(3e9) assert.Len(t, registryDirectory.cacheInvokers, 3) assert.Equal(t, true, registryDirectory.IsAvailable()) @@ -116,11 +119,12 @@ func Test_Destroy(t *testing.T) { func Test_List(t *testing.T) { registryDirectory, _ := normalRegistryDir() - time.Sleep(1e9) + time.Sleep(4e9) assert.Len(t, registryDirectory.List(&invocation.RPCInvocation{}), 3) assert.Equal(t, true, registryDirectory.IsAvailable()) } + func Test_MergeProviderUrl(t *testing.T) { registryDirectory, mockRegistry := normalRegistryDir(true) providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go index 79e3ad514584937e742db4bbc993202dd6a9f5b9..f9b046a2c52814cd4e5ea38f9ea4c58c8bdb5bc4 100644 --- a/registry/etcdv3/listener.go +++ b/registry/etcdv3/listener.go @@ -83,6 +83,7 @@ func NewConfigurationListener(reg *etcdV3Registry) *configurationListener { reg.WaitGroup().Add(1) return &configurationListener{registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)} } + func (l *configurationListener) Process(configType *config_center.ConfigChangeEvent) { l.events <- configType } @@ -108,6 +109,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } } } + func (l *configurationListener) Close() { l.registry.WaitGroup().Done() } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 748b8204d97e60c9803821290184fc5717c41025..a7678ba4e2f38cfeb77f202103e03066a7efdbef 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -77,6 +77,7 @@ func newRegistryProtocol() *registryProtocol { bounds: &sync.Map{}, } } + func getRegistry(regUrl *common.URL) registry.Registry { reg, err := extension.GetRegistry(regUrl.Protocol, regUrl) if err != nil { @@ -85,13 +86,14 @@ func getRegistry(regUrl *common.URL) registry.Registry { } return reg } + func (proto *registryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } -func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -115,6 +117,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { serviceUrl.String(), err.Error()) return nil } + err = reg.Register(*serviceUrl) if err != nil { logger.Errorf("consumer service %v register registry %v error, error message is %s", @@ -131,7 +134,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { } func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { - proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -172,13 +174,14 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte wrappedInvoker := newWrappedInvoker(invoker, providerUrl) cachedExporter = extension.GetProtocol(protocolwrapper.FILTER).Export(wrappedInvoker) proto.bounds.Store(key, cachedExporter) - logger.Infof("The exporter has not been cached, and will return a new exporter!") + logger.Infof("The exporter has not been cached, and will return a new exporter!") } go reg.Subscribe(overriderUrl, overrideSubscribeListener) return cachedExporter.(protocol.Exporter) } + func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) @@ -202,12 +205,14 @@ type overrideSubscribeListener struct { func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } + func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) { if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd { nl.configurator = extension.GetDefaultConfigurator(&(event.Service)) nl.doOverrideIfNecessary() } } + func (nl *overrideSubscribeListener) doOverrideIfNecessary() { providerUrl := getProviderUrl(nl.originInvoker) key := getCacheKey(providerUrl) @@ -276,6 +281,7 @@ func isMatched(providerUrl *common.URL, consumerUrl *common.URL) bool { consumerVersion == providerVersion) && (len(consumerClassifier) == 0 || consumerClassifier == constant.ANY_VALUE || consumerClassifier == providerClassifier) } + func isMatchCategory(category string, categories string) bool { if len(categories) == 0 { return category == constant.DEFAULT_CATEGORY @@ -287,6 +293,7 @@ func isMatchCategory(category string, categories string) bool { return strings.Contains(categories, category) } } + func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { newUrl := providerUrl.Clone() newUrl.Protocol = constant.PROVIDER_PROTOCOL @@ -334,6 +341,7 @@ func getProviderUrl(invoker protocol.Invoker) *common.URL { //be careful params maps in url is map type return url.SubURL.Clone() } + func setProviderUrl(regURL *common.URL, providerURL *common.URL) { regURL.SubURL = providerURL } diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go index de57a0afa7529dd5c77c1fe5440b336cdd212fca..cee2a6a625368f655d1b9bc5fe8cc37031e1aef7 100644 --- a/registry/protocol/protocol_test.go +++ b/registry/protocol/protocol_test.go @@ -44,6 +44,7 @@ import ( func init() { config.SetProviderConfig(config.ProviderConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) } + func referNormal(t *testing.T, regProtocol *registryProtocol) { extension.SetProtocol("registry", GetProtocol) extension.SetRegistry("mock", registry.NewMockRegistry) diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index 588e0c519288ed32a2453fac87d226b41d4a5194..fe8e42db9f39190e34142149a6b67c9638a84ed2 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -56,7 +56,7 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { // Intercept the last bit index := strings.Index(eventType.Path, "/providers/") if index == -1 { - logger.Warn("Listen with no url, event.path={%v}", eventType.Path) + logger.Warnf("Listen with no url, event.path={%v}", eventType.Path) return false } url := eventType.Path[index+len("/providers/"):] diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index f4e53dcc4219d947fea93a10bccc420811afd2b9..e13443d57d7dae9fb5d50b2e1c28f618780fd850 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -141,6 +141,7 @@ func (r *zkRegistry) CloseAndNilClient() { r.client.Close() r.client = nil } + func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient { return r.client } diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go index 8f9b80cd30a9791709a0b2e83b9e59e0046f4c6c..d9166fc8eac78b8d1c5a93f05b7cf5fc9705e10f 100644 --- a/remoting/etcdv3/client_test.go +++ b/remoting/etcdv3/client_test.go @@ -31,6 +31,7 @@ import ( import ( "github.com/coreos/etcd/mvcc/mvccpb" perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "go.etcd.io/etcd/embed" "google.golang.org/grpc/connectivity" @@ -171,6 +172,10 @@ func (suite *ClientTestSuite) TestClientDone() { }() c.Wait.Wait() + + if c.Valid() == true { + suite.T().Fatal("client should be invalid then") + } } func (suite *ClientTestSuite) TestClientCreateKV() { @@ -295,13 +300,26 @@ func (suite *ClientTestSuite) TestClientWatch() { t.Fatal(err) } + events := make([]mvccpb.Event, 0) + var eCreate, eDelete mvccpb.Event + for e := range wc { for _, event := range e.Events { + events = append(events, (mvccpb.Event)(*event)) + if event.Type == mvccpb.PUT { + eCreate = (mvccpb.Event)(*event) + } + if event.Type == mvccpb.DELETE { + eDelete = (mvccpb.Event)(*event) + } t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value) } } + assert.Equal(t, 2, len(events)) + assert.Contains(t, events, eCreate) + assert.Contains(t, events, eDelete) }() for _, tc := range tests { @@ -334,25 +352,35 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() { wg.Add(1) go func() { + defer wg.Done() + completePath := path.Join("scott", "wang") wc, err := observeC.watch(completePath) if err != nil { t.Fatal(err) } + events := make([]mvccpb.Event, 0) + var eCreate, eDelete mvccpb.Event + for e := range wc { for _, event := range e.Events { - + events = append(events, (mvccpb.Event)(*event)) if event.Type == mvccpb.DELETE { + eDelete = (mvccpb.Event)(*event) t.Logf("complete key (%s) is delete", completePath) - wg.Done() observeC.Close() - return + break } + eCreate = (mvccpb.Event)(*event) t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value) } } + + assert.Equal(t, 2, len(events)) + assert.Contains(t, events, eCreate) + assert.Contains(t, events, eDelete) }() _, err := c.RegisterTemp("scott", "wang") diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index f95231b374230c93036e0fbd74aeca4ecfe57f46..21486aab59c3f9b44c25b68d7433f864a990149a 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -412,7 +412,7 @@ func (z *ZookeeperClient) Create(basePath string) error { if err != nil { if err == zk.ErrNodeExists { - logger.Infof("zk.create(\"%s\") exists\n", tmpPath) + logger.Debugf("zk.create(\"%s\") exists\n", tmpPath) } else { logger.Errorf("zk.create(\"%s\") error(%v)\n", tmpPath, perrors.WithStack(err)) return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index 97ea775652cf82ce86388fb376832ccb7e07a205..a41f6cd3230700332519ce1c2d3489bfcc4b6ef0 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -70,6 +70,7 @@ func (r *mockFacade) Destroy() { func (r *mockFacade) RestartCallBack() bool { return true } + func (r *mockFacade) IsAvailable() bool { return true } diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 0d1d4bd27a430a6211a960ab1ca882a73f91a01e..77aa05ee9eada327475fa5bf86c7af2c65de0ef2 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -245,7 +245,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi if err != nil { logger.Errorf("Get new node path {%v} 's content error,message is {%v}", dubboPath, perrors.WithStack(err)) } - logger.Infof("Get children!{%s}", dubboPath) + logger.Debugf("Get children!{%s}", dubboPath) if !listener.DataChange(remoting.Event{Path: dubboPath, Action: remoting.EventTypeAdd, Content: string(content)}) { continue } diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go index 43e9aca3f44470873c3c97ec2447bebcc57e5545..7301cd52c392b6950b3a49f78e8124eae532b083 100644 --- a/remoting/zookeeper/listener_test.go +++ b/remoting/zookeeper/listener_test.go @@ -66,6 +66,7 @@ func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Even return ts, client, event } + func TestListener(t *testing.T) { changedData := ` dubbo.consumer.request_timeout=3s