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 f387b934e25e0b19c6d4c6cb9b23548344c3d543..1dde951d350e6ee51f3f2aeeac7bb516b1b999be 100644 --- a/README.md +++ b/README.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 Implementation. @@ -13,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 ## @@ -42,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) @@ -65,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) @@ -79,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 @@ -93,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 @@ -124,7 +127,7 @@ Windows before_ut.bat ``` -# Run +### Run ```bash go test ./... @@ -132,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). @@ -146,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日 捐献给Apache之后的第一次release](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) 贡献代码或者文档,我们都热烈欢迎。具体请参考 [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md)。 @@ -145,8 +152,5 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic 若你正在使用 [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..fcf4ef2df614147ecf5ed270293c7c4a15297f90 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -15,7 +15,17 @@ :: 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 +md remoting\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% + +md config_center\zookeeper\zookeeper-4unittest\contrib\fatjar 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 + +md registry\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\chain\zookeeper-4unittest\contrib\fatjar +xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\condition\zookeeper-4unittest\contrib\fatjar +xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%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..c6559cba31310a9957259cd9619eb42977649edd --- a/before_ut.sh +++ b/before_ut.sh @@ -15,7 +15,17 @@ # limitations under the License. -mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar +mkdir -p remoting/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 + +mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar 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 + +mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar +cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar +cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar cluster/router/chain/zookeeper-4unittest/contrib/fatjar + +mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar +cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar cluster/router/condition/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/cluster_impl/available_cluster_invoker_test.go b/cluster/cluster_impl/available_cluster_invoker_test.go index de04db1da4e8e6df12960b1a2ee81b0044379d6f..dc0666d5afa633c86fdfaa48b93a334c19bb0248 100644 --- a/cluster/cluster_impl/available_cluster_invoker_test.go +++ b/cluster/cluster_impl/available_cluster_invoker_test.go @@ -39,7 +39,7 @@ import ( ) var ( - availableUrl, _ = common.NewURL(context.Background(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + availableUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) func registerAvailable(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { 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 d06d3cc23e75cf2227fa22894475f141ffe09a96..d074697b85a3cf5b770de90da4847043d98c9df1 100644 --- a/cluster/cluster_impl/base_cluster_invoker_test.go +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -18,7 +18,6 @@ package cluster_impl import ( - "context" "fmt" "testing" ) @@ -37,7 +36,7 @@ import ( func Test_StickyNormal(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) url.SetParam("sticky", "true") invokers = append(invokers, NewMockInvoker(url, 1)) } @@ -48,10 +47,11 @@ 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++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) url.SetParam("sticky", "true") invokers = append(invokers, NewMockInvoker(url, 1)) } diff --git a/cluster/cluster_impl/broadcast_cluster_invoker_test.go b/cluster/cluster_impl/broadcast_cluster_invoker_test.go index b20d962e2cffb34d0a151488a1bdf63499e4de86..1de5270265a79b4d1d62317730bd06927d939acd 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker_test.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker_test.go @@ -39,7 +39,7 @@ import ( ) var ( - broadcastUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + broadcastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) func registerBroadcast(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker { diff --git a/cluster/cluster_impl/failback_cluster_test.go b/cluster/cluster_impl/failback_cluster_test.go index 895077922a88abc05416e58459205b449831ac56..4571fccec59a2f995009f57c35b56ec5e1cb3ea6 100644 --- a/cluster/cluster_impl/failback_cluster_test.go +++ b/cluster/cluster_impl/failback_cluster_test.go @@ -41,7 +41,7 @@ import ( ) var ( - failbackUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failbackUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) // registerFailback register failbackCluster to cluster extension. diff --git a/cluster/cluster_impl/failfast_cluster_test.go b/cluster/cluster_impl/failfast_cluster_test.go index 9585f03b7fa8f45a19c7c47e04dcd57cc1e4bb11..38e258199e05c396e22118337bbdb3ae2b955bd0 100644 --- a/cluster/cluster_impl/failfast_cluster_test.go +++ b/cluster/cluster_impl/failfast_cluster_test.go @@ -39,7 +39,7 @@ import ( ) var ( - failfastUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failfastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) // registerFailfast register failfastCluster to cluster extension. diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index 46b7b28e0299b669f5ec48ed024e7aa80c39e3d8..1be21067a6a9045cb6ae6f84655d516fea1f844b 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -39,9 +39,9 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -///////////////////////////// +// /////////////////////////// // mock invoker -///////////////////////////// +// /////////////////////////// type MockInvoker struct { url common.URL @@ -107,7 +107,7 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i), common.WithParams(urlParam)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i), common.WithParams(urlParam)) invokers = append(invokers, NewMockInvoker(url, successCount)) } @@ -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) @@ -157,7 +158,7 @@ func Test_FailoverDestroy(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.Background(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, NewMockInvoker(url, 1)) } diff --git a/cluster/cluster_impl/failsafe_cluster_test.go b/cluster/cluster_impl/failsafe_cluster_test.go index 234995b8e522124fe9beff0937ca23a63aa63844..2e35de8da91cc78730b6380bf039f0626ca75ec0 100644 --- a/cluster/cluster_impl/failsafe_cluster_test.go +++ b/cluster/cluster_impl/failsafe_cluster_test.go @@ -39,7 +39,7 @@ import ( ) var ( - failsafeUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failsafeUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) // registerFailsafe register failsafeCluster to cluster extension. diff --git a/cluster/cluster_impl/forking_cluster_test.go b/cluster/cluster_impl/forking_cluster_test.go index d819781eb23631e6b8eef76e5bdf7d7837f43d53..9797ecbd041ae2e09c3d0566f88d08b7246b900f 100644 --- a/cluster/cluster_impl/forking_cluster_test.go +++ b/cluster/cluster_impl/forking_cluster_test.go @@ -42,7 +42,7 @@ import ( ) var ( - forkingUrl, _ = common.NewURL(context.TODO(), "dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + forkingUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") ) func registerForking(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker { diff --git a/cluster/cluster_impl/registry_aware_cluster_test.go b/cluster/cluster_impl/registry_aware_cluster_test.go index 7f916c1aaa5609beb3d818e08f5b0950c3273e6d..3d0dcc0159839eb0a08aed842ee084449458c645 100644 --- a/cluster/cluster_impl/registry_aware_cluster_test.go +++ b/cluster/cluster_impl/registry_aware_cluster_test.go @@ -39,7 +39,7 @@ func Test_RegAwareInvokeSuccess(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.Background(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, NewMockInvoker(url, 1)) } @@ -55,7 +55,7 @@ func TestDestroy(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.Background(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, NewMockInvoker(url, 1)) } 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 3ce772e8a6287aebef3fdad039e2b12be421c4e3..c50c9a4063bd1a372c27e47687cbf63850f76cef 100644 --- a/cluster/directory/static_directory_test.go +++ b/cluster/directory/static_directory_test.go @@ -18,7 +18,6 @@ package directory import ( - "context" "fmt" "testing" ) @@ -36,18 +35,20 @@ import ( func Test_StaticDirList(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } 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) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go index 174d5715dd1258d329f40251e76ca47d98791ea9..a44293172c6e2c96bd098a19306f69260b713689 100644 --- a/cluster/loadbalance/consistent_hash_test.go +++ b/cluster/loadbalance/consistent_hash_test.go @@ -18,7 +18,6 @@ package loadbalance import ( - "context" "testing" ) @@ -44,7 +43,7 @@ type consistentHashSelectorSuite struct { func (s *consistentHashSelectorSuite) SetupTest() { var invokers []protocol.Invoker - url, _ := common.NewURL(context.TODO(), + url, _ := common.NewURL( "dubbo://192.168.1.0:20000/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") invokers = append(invokers, protocol.NewBaseInvoker(url)) s.selector = newConsistentHashSelector(invokers, "echo", 999944) @@ -56,8 +55,8 @@ func (s *consistentHashSelectorSuite) TestToKey() { } func (s *consistentHashSelectorSuite) TestSelectForKey() { - url1, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:8080") - url2, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:8081") + url1, _ := common.NewURL("dubbo://192.168.1.0:8080") + url2, _ := common.NewURL("dubbo://192.168.1.0:8081") s.selector.virtualInvokers = make(map[uint32]protocol.Invoker) s.selector.virtualInvokers[99874] = protocol.NewBaseInvoker(url1) s.selector.virtualInvokers[9999945] = protocol.NewBaseInvoker(url2) @@ -84,11 +83,11 @@ type consistentHashLoadBalanceSuite struct { func (s *consistentHashLoadBalanceSuite) SetupTest() { var err error - s.url1, err = common.NewURL(context.TODO(), "dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url1, err = common.NewURL("dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") s.NoError(err) - s.url2, err = common.NewURL(context.TODO(), "dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url2, err = common.NewURL("dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") s.NoError(err) - s.url3, err = common.NewURL(context.TODO(), "dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url3, err = common.NewURL("dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") s.NoError(err) s.invoker1 = protocol.NewBaseInvoker(s.url1) diff --git a/cluster/loadbalance/least_active_test.go b/cluster/loadbalance/least_active_test.go index 6bc6985678d7c392ad21b49ec341c3550265f622..54e57e930f17008cf6d767ef47c0e754ac85d8f7 100644 --- a/cluster/loadbalance/least_active_test.go +++ b/cluster/loadbalance/least_active_test.go @@ -18,7 +18,6 @@ package loadbalance import ( - "context" "fmt" "testing" ) @@ -38,13 +37,13 @@ func TestLeastActiveSelect(t *testing.T) { var invokers []protocol.Invoker - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService") invokers = append(invokers, protocol.NewBaseInvoker(url)) i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) for i := 1; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } loadBalance.Select(invokers, &invocation.RPCInvocation{}) @@ -56,7 +55,7 @@ func TestLeastActiveByWeight(t *testing.T) { var invokers []protocol.Invoker loop := 3 for i := 1; i <= loop; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("test%v://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i, i)) + url, _ := common.NewURL(fmt.Sprintf("test%v://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i, i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } diff --git a/cluster/loadbalance/random_test.go b/cluster/loadbalance/random_test.go index ffe65d78ac61e5210d23e44c7f802597fed78f96..ff876f4aef8d229e8041594aaaa096f3ad5b1834 100644 --- a/cluster/loadbalance/random_test.go +++ b/cluster/loadbalance/random_test.go @@ -18,7 +18,6 @@ package loadbalance import ( - "context" "fmt" "net/url" "strconv" @@ -42,13 +41,13 @@ func Test_RandomlbSelect(t *testing.T) { invokers := []protocol.Invoker{} - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", 0)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", 0)) invokers = append(invokers, protocol.NewBaseInvoker(url)) i := randomlb.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) for i := 1; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } randomlb.Select(invokers, &invocation.RPCInvocation{}) @@ -59,13 +58,13 @@ func Test_RandomlbSelectWeight(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } urlParams := url.Values{} urlParams.Set("methods.test."+constant.WEIGHT_KEY, "10000000000000") - urll, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) + urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) invokers = append(invokers, protocol.NewBaseInvoker(urll)) ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test")) @@ -80,7 +79,7 @@ func Test_RandomlbSelectWeight(t *testing.T) { } assert.Condition(t, func() bool { - //really is 0.9999999999999 + // really is 0.9999999999999 return selected/10000 > 0.9 }) } @@ -90,13 +89,13 @@ func Test_RandomlbSelectWarmup(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } urlParams := url.Values{} urlParams.Set(constant.REMOTE_TIMESTAMP_KEY, strconv.FormatInt(time.Now().Add(time.Minute*(-9)).Unix(), 10)) - urll, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) + urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) invokers = append(invokers, protocol.NewBaseInvoker(urll)) ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test")) diff --git a/cluster/loadbalance/round_robin_test.go b/cluster/loadbalance/round_robin_test.go index 0b8e89c7349afb056309b5a70d91aa5cce309aa0..1517f2a20b473af57cc23e61b988aa5a6a04de31 100644 --- a/cluster/loadbalance/round_robin_test.go +++ b/cluster/loadbalance/round_robin_test.go @@ -18,7 +18,6 @@ package loadbalance import ( - "context" "fmt" "strconv" "testing" @@ -39,13 +38,13 @@ func TestRoundRobinSelect(t *testing.T) { var invokers []protocol.Invoker - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService") invokers = append(invokers, protocol.NewBaseInvoker(url)) i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) for i := 1; i < 10; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } loadBalance.Select(invokers, &invocation.RPCInvocation{}) @@ -57,7 +56,7 @@ func TestRoundRobinByWeight(t *testing.T) { var invokers []protocol.Invoker loop := 10 for i := 1; i <= loop; i++ { - url, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i)) + url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService?weight=%v", i, i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } 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 55% rename from cluster/router/condition_router_test.go rename to cluster/router/condition/factory_test.go index 43e74317e3e07caff4a6e7021352ca417e66ccfb..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" @@ -59,21 +59,21 @@ func (bi *MockInvoker) GetUrl() common.URL { } func getRouteUrl(rule string) *common.URL { - url, _ := common.NewURL(context.TODO(), "condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") url.AddParam("rule", rule) url.AddParam("force", "true") return &url } func getRouteUrlWithForce(rule, force string) *common.URL { - url, _ := common.NewURL(context.TODO(), "condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") url.AddParam("rule", rule) url.AddParam("force", force) return &url } func getRouteUrlWithNoForce(rule string) *common.URL { - url, _ := common.NewURL(context.TODO(), "condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") url.AddParam("rule", rule) return &url } @@ -119,42 +119,42 @@ 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)) - cUrl, _ := common.NewURL(context.TODO(), "consumer://1.1.1.1/com.foo.BarService") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(cUrl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService") + 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) } func TestRoute_matchFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() t.Logf("The local ip is %s", localIP) - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invokers := []protocol.Invoker{NewMockInvoker(url1, 1), NewMockInvoker(url2, 2), NewMockInvoker(url3, 3)} rule1 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.3")) rule2 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.* & host != 10.20.3.3")) @@ -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)) - cUrl, _ := common.NewURL(context.TODO(), "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{}) + 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{}) assert.Equal(t, 1, len(fileredInvokers1)) assert.Equal(t, 0, len(fileredInvokers2)) assert.Equal(t, 0, len(fileredInvokers3)) @@ -187,75 +187,80 @@ 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)) - url, _ := common.NewURL(context.TODO(), "consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(url, inv) + 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) assert.Equal(t, true, matchWhen) - url1, _ := common.NewURL(context.TODO(), "consumer://1.1.1.1/com.foo.BarService?methods=getFoo") - matchWhen, _ = router.(*ConditionRouter).MatchWhen(url1, inv) + url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") + matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) assert.Equal(t, true, matchWhen) - url2, _ := common.NewURL(context.TODO(), "consumer://1.1.1.1/com.foo.BarService?methods=getFoo") + 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(context.TODO(), "consumer://1.1.1.1/com.foo.BarService?methods=getFoo") + 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) } func TestRoute_ReturnFalse(t *testing.T) { - url, _ := common.NewURL(context.TODO(), "") + url, _ := common.NewURL("") localIP, _ := gxnet.GetLocalIP() invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } func TestRoute_ReturnEmpty(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url, _ := common.NewURL(context.TODO(), "") + url, _ := common.NewURL("") invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } func TestRoute_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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]) @@ -263,18 +268,18 @@ func TestRoute_HostFilter(t *testing.T) { func TestRoute_Empty_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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]) @@ -282,18 +287,18 @@ func TestRoute_Empty_HostFilter(t *testing.T) { func TestRoute_False_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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]) @@ -301,18 +306,18 @@ func TestRoute_False_HostFilter(t *testing.T) { func TestRoute_Placeholder(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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]) @@ -320,34 +325,44 @@ func TestRoute_Placeholder(t *testing.T) { func TestRoute_NoForce(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithNoForce(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithNoForce(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } func TestRoute_Force(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL(context.TODO(), "dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(context.TODO(), fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") + url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) - curl, _ := common.NewURL(context.TODO(), "consumer://"+localIP+"/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithForce(rule, "true")) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + 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/default.go b/common/constant/default.go index 992fc32748bb4fc7777cffecc9137663c681c3f7..8ed645e84a724531080eff6efe5fdb0df5479e80 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -66,6 +66,7 @@ const ( DYNAMIC_CONFIGURATORS_CATEGORY = "dynamicconfigurators" APP_DYNAMIC_CONFIGURATORS_CATEGORY = "appdynamicconfigurators" PROVIDER_CATEGORY = "providers" + CONSUMER_CATEGORY = "consumers" ) const ( 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 eff704371c7c5b66ca11a846ad7603a01f8b5708..c8a03b3be9f0179bb5317640d38abef8d9cc2b3a 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 ( CONSUMER_SIGN_FILTER = "sign" PROVIDER_AUTH_FILTER = "auth" @@ -159,3 +190,25 @@ const ( ACCESS_KEY_ID_KEY = "accessKeyId" 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 b2a514b4e6b1ba105e9f9aa4f9501bce1e613d4d..ebb648db27c3efff534f0d0a545f2211f335aa89 100644 --- a/common/url.go +++ b/common/url.go @@ -19,7 +19,6 @@ package common import ( "bytes" - "context" "encoding/base64" "fmt" "math" @@ -60,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 ... @@ -85,7 +84,6 @@ type baseUrl struct { paramsLock sync.RWMutex params url.Values PrimitiveURL string - ctx context.Context } // URL ... @@ -194,14 +192,15 @@ func NewURLWithOptions(opts ...option) *URL { return url } -// NewURL ... -func NewURL(ctx context.Context, urlString string, opts ...option) (URL, error) { +// NewURL will create a new url +// the urlString should not be empty +func NewURL(urlString string, opts ...option) (URL, error) { var ( err error rawUrlString string serviceUrl *url.URL - s = URL{baseUrl: baseUrl{ctx: ctx}} + s = URL{baseUrl: baseUrl{}} ) // new a null instance @@ -216,7 +215,7 @@ func NewURL(ctx context.Context, urlString string, opts ...option) (URL, error) //rawUrlString = "//" + rawUrlString if strings.Index(rawUrlString, "//") < 0 { - t := URL{baseUrl: baseUrl{ctx: ctx}} + t := URL{baseUrl: baseUrl{}} for _, opt := range opts { opt(&t) } @@ -241,7 +240,7 @@ func NewURL(ctx context.Context, 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 { @@ -278,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 @@ -289,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 { @@ -367,11 +368,6 @@ func (c *URL) EncodedServiceKey() string { return strings.Replace(serviceKey, "/", "*", 1) } -// Context ... -func (c URL) Context() context.Context { - return c.ctx -} - // Service ... func (c URL) Service() string { service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) @@ -379,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 9d80bc52b8ee5c74abb29b3b65487156ebf6ae2c..2372de520e88b0949023e88cec64871736dd6aa0 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -18,7 +18,6 @@ package common import ( - "context" "encoding/base64" "net/url" "testing" @@ -56,10 +55,10 @@ func TestNewURLWithOptions(t *testing.T) { } func TestURL(t *testing.T) { - u, err := NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + u, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) @@ -83,7 +82,7 @@ func TestURL(t *testing.T) { } func TestURLWithoutSchema(t *testing.T) { - u, err := NewURL(context.TODO(), "127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ + u, err := NewURL("127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ @@ -110,13 +109,13 @@ func TestURLWithoutSchema(t *testing.T) { } func TestURL_URLEqual(t *testing.T) { - u1, err := NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0") + u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0") assert.NoError(t, err) - u2, err := NewURL(context.TODO(), "dubbo://127.0.0.2:20001/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0") + u2, err := NewURL("dubbo://127.0.0.2:20001/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0") assert.NoError(t, err) assert.True(t, u1.URLEqual(u2)) - u3, err := NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + u3, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") assert.NoError(t, err) assert.False(t, u1.URLEqual(u3)) } @@ -165,8 +164,9 @@ func TestURL_GetParamAndDecoded(t *testing.T) { v, _ := u.GetParamAndDecoded("rule") assert.Equal(t, rule, v) } + func TestURL_GetRawParam(t *testing.T) { - u, _ := NewURL(context.TODO(), "condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") + u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" u.Password = "test" assert.Equal(t, "condition", u.GetRawParam("protocol")) @@ -177,8 +177,9 @@ 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(context.TODO(), "condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") + u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" u.Password = "test" @@ -240,8 +241,8 @@ func TestMergeUrl(t *testing.T) { serviceUrlParams.Set(constant.CLUSTER_KEY, "roundrobin") serviceUrlParams.Set(constant.RETRIES_KEY, "2") serviceUrlParams.Set(constant.METHOD_KEYS+".testMethod."+constant.RETRIES_KEY, "2") - referenceUrl, _ := NewURL(context.TODO(), "mock1://127.0.0.1:1111", WithParams(referenceUrlParams), WithMethods([]string{"testMethod"})) - serviceUrl, _ := NewURL(context.TODO(), "mock2://127.0.0.1:20000", WithParams(serviceUrlParams)) + referenceUrl, _ := NewURL("mock1://127.0.0.1:1111", WithParams(referenceUrlParams), WithMethods([]string{"testMethod"})) + serviceUrl, _ := NewURL("mock2://127.0.0.1:20000", WithParams(serviceUrlParams)) mergedUrl := MergeUrl(&serviceUrl, &referenceUrl) assert.Equal(t, "random", mergedUrl.GetParam(constant.CLUSTER_KEY, "")) @@ -252,7 +253,7 @@ func TestMergeUrl(t *testing.T) { } func TestURL_SetParams(t *testing.T) { - u1, err := NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") + u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") assert.NoError(t, err) params := url.Values{} params.Set("key", "3") @@ -262,7 +263,7 @@ func TestURL_SetParams(t *testing.T) { } func TestClone(t *testing.T) { - u1, err := NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") + u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") assert.NoError(t, err) u2 := u1.Clone() assert.Equal(t, u2.Protocol, "dubbo") @@ -273,7 +274,7 @@ func TestClone(t *testing.T) { } func TestColonSeparatedKey(t *testing.T) { - u1, _ := NewURL(context.TODO(), "dubbo://127.0.0.1:20000") + u1, _ := NewURL("dubbo://127.0.0.1:20000") u1.AddParam(constant.INTERFACE_KEY, "com.ikurento.user.UserProvider") assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+"::") diff --git a/config/base_config.go b/config/base_config.go index f38ef9b953997888ce47a7bc8aaa2e6577cfe377..6d5ec7e2498ba65b2a6833b6c9cefcb3394e60df 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -18,7 +18,8 @@ package config import ( - "context" + "io/ioutil" + "path" "reflect" "strconv" "strings" @@ -26,6 +27,7 @@ import ( import ( perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" ) import ( @@ -50,8 +52,11 @@ type BaseConfig struct { MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"` } -func (c *BaseConfig) startConfigCenter(ctx context.Context) error { - url, err := common.NewURL(ctx, c.ConfigCenterConfig.Address, common.WithProtocol(c.ConfigCenterConfig.Protocol), common.WithParams(c.ConfigCenterConfig.GetUrlMap())) +// startConfigCenter will start the config center. +// it will prepare the environment +func (c *BaseConfig) startConfigCenter() error { + url, err := common.NewURL(c.ConfigCenterConfig.Address, + common.WithProtocol(c.ConfigCenterConfig.Protocol), common.WithParams(c.ConfigCenterConfig.GetUrlMap())) if err != nil { return err } @@ -136,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() @@ -145,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 != "" { @@ -298,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() { @@ -358,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 ab2769578072387e4686593f3c2c10fb8e49731d..6973a4a18b5e3a78d9039bc818a5a2a046613783 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -17,8 +17,8 @@ package config import ( - "context" "fmt" + "path/filepath" "reflect" "testing" ) @@ -492,7 +492,7 @@ func Test_startConfigCenter(t *testing.T) { Group: "dubbo", ConfigFile: "mockDubbo.properties", }} - err := c.startConfigCenter(context.Background()) + err := c.startConfigCenter() assert.NoError(t, err) b, v := config.GetEnvInstance().Configuration().Back().Value.(*config.InmemoryConfiguration).GetProperty("dubbo.application.organization") assert.True(t, b) @@ -518,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/consumer_config.go b/config/consumer_config.go index 1bfa761fc9e4f88373163e26379d21639693aadf..94da301ce45acedb720120d56dc07bf76c780d7f 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -18,9 +18,6 @@ package config import ( - "context" - "io/ioutil" - "path" "time" ) @@ -28,7 +25,6 @@ import ( "github.com/creasty/defaults" "github.com/dubbogo/getty" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( @@ -91,16 +87,8 @@ func ConsumerInit(confConFile string) error { return perrors.Errorf("application configure(consumer) file name is nil") } - if path.Ext(confConFile) != ".yml" { - return perrors.Errorf("application configure file name{%v} suffix must be .yml", confConFile) - } - - confFileStream, err := ioutil.ReadFile(confConFile) - if err != nil { - return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confConFile, perrors.WithStack(err)) - } consumerConfig = &ConsumerConfig{} - err = yaml.Unmarshal(confFileStream, consumerConfig) + err := unmarshalYMLConfig(confConFile, consumerConfig) if err != nil { return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) } @@ -136,7 +124,7 @@ func configCenterRefreshConsumer() error { var err error if consumerConfig.ConfigCenterConfig != nil { consumerConfig.SetFatherConfig(consumerConfig) - if err := consumerConfig.startConfigCenter(context.Background()); err != nil { + if err := consumerConfig.startConfigCenter(); err != nil { return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) } consumerConfig.fresh() 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/provider_config.go b/config/provider_config.go index 0f5c71a7de055219992c433f7fcc5d639e4b3503..a36fd4d0a07c3203e53582cbf2f3442d880a3981 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -17,16 +17,9 @@ package config -import ( - "context" - "io/ioutil" - "path" -) - import ( "github.com/creasty/defaults" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( @@ -82,16 +75,8 @@ func ProviderInit(confProFile string) error { return perrors.Errorf("application configure(provider) file name is nil") } - if path.Ext(confProFile) != ".yml" { - return perrors.Errorf("application configure file name{%v} suffix must be .yml", confProFile) - } - - confFileStream, err := ioutil.ReadFile(confProFile) - if err != nil { - return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) - } providerConfig = &ProviderConfig{} - err = yaml.Unmarshal(confFileStream, providerConfig) + err := unmarshalYMLConfig(confProFile, providerConfig) if err != nil { return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) } @@ -114,7 +99,7 @@ func configCenterRefreshProvider() error { //fresh it if providerConfig.ConfigCenterConfig != nil { providerConfig.fatherConfig = providerConfig - if err := providerConfig.startConfigCenter(context.Background()); err != nil { + if err := providerConfig.startConfigCenter(); err != nil { return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) } providerConfig.fresh() diff --git a/config/reference_config.go b/config/reference_config.go index 07b7e8f125c4a31b668d06bfcea8cb835385fd9a..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,11 +100,11 @@ 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(context.Background(), urlStr) + serviceUrl, err := common.NewURL(urlStr) if err != nil { panic(fmt.Sprintf("user specified URL %v refer error, error message is %v ", urlStr, err.Error())) } @@ -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.go b/config/registry_config.go index c347c2c2348018a66114c56a1c982d57d4f2783f..4e4b6e97d79a9402616b6cac954f7a09b2973dcc 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -18,7 +18,6 @@ package config import ( - "context" "net/url" "strconv" "strings" @@ -95,9 +94,7 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf addresses := strings.Split(registryConf.Address, ",") address := addresses[0] address = traslateRegistryConf(address, registryConf) - url, err = common.NewURL( - context.Background(), - constant.REGISTRY_PROTOCOL+"://"+address, + url, err = common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address, common.WithParams(registryConf.getUrlMap(roleType)), common.WithUsername(registryConf.Username), common.WithPassword(registryConf.Password), 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/apollo/impl_test.go b/config_center/apollo/impl_test.go index 84e2b28bac4eace18ec7269249b67c938b96232e..a95524b41b887313993aad4e774ed6d96b24c08f 100644 --- a/config_center/apollo/impl_test.go +++ b/config_center/apollo/impl_test.go @@ -17,7 +17,6 @@ package apollo import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -141,7 +140,7 @@ func serviceConfigResponse(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "%s", result) } -//run mock config server +// run mock config server func runMockConfigServer(handlerMap map[string]func(http.ResponseWriter, *http.Request), notifyHandler func(http.ResponseWriter, *http.Request)) *httptest.Server { uriHandlerMap := make(map[string]func(http.ResponseWriter, *http.Request), 0) @@ -196,7 +195,7 @@ func initMockApollo(t *testing.T) *apolloConfiguration { }} apollo := initApollo() apolloUrl := strings.ReplaceAll(apollo.URL, "http", "apollo") - url, err := common.NewURL(context.TODO(), apolloUrl, common.WithParams(c.ConfigCenterConfig.GetUrlMap())) + url, err := common.NewURL(apolloUrl, common.WithParams(c.ConfigCenterConfig.GetUrlMap())) assert.NoError(t, err) configuration, err := newApolloConfiguration(&url) assert.NoError(t, err) @@ -268,7 +267,7 @@ func (l *apolloDataListener) Process(configType *config_center.ConfigChangeEvent } func deleteMockJson(t *testing.T) { - //because the file write in another goroutine,so have a break ... + // because the file write in another goroutine,so have a break ... time.Sleep(100 * time.Millisecond) remove := os.Remove("mockDubbog.properties.json") t.Log("remove result:", remove) 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 b8f417b4602e135d114be99637061851088d4e44..c0aeb15130e7862fcb00d6cb82cbef60df777acb 100644 --- a/config_center/configurator/override_test.go +++ b/config_center/configurator/override_test.go @@ -17,7 +17,6 @@ package configurator import ( - "context" "testing" ) @@ -32,36 +31,36 @@ import ( ) func Test_configureVerison2p6(t *testing.T) { - url, err := common.NewURL(context.Background(), "override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") + url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") assert.NoError(t, err) configurator := extension.GetConfigurator("default", &url) assert.Equal(t, "override", configurator.GetUrl().Protocol) - providerUrl, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") + providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") 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(context.Background(), "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") + 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) configurator := extension.GetConfigurator("default", &url) assert.Equal(t, "override", configurator.GetUrl().Protocol) - providerUrl, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") + providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") 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(context.Background(), "override://127.0.0.1:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") + 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) configurator := extension.GetConfigurator("default", &url) assert.Equal(t, "override", configurator.GetUrl().Protocol) - providerUrl, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") + providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, "")) @@ -69,11 +68,11 @@ func Test_configureVerison2p6WithIp(t *testing.T) { } func Test_configureVerison2p7(t *testing.T) { - url, err := common.NewURL(context.Background(), "jsonrpc://0.0.0.0:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&configVersion=1.0&side=provider") + url, err := common.NewURL("jsonrpc://0.0.0.0:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&configVersion=1.0&side=provider") assert.NoError(t, err) configurator := extension.GetConfigurator("default", &url) - providerUrl, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") + providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, "")) 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 new file mode 100644 index 0000000000000000000000000000000000000000..1bf61a942ba9f7530b495a57465c4ee6cb0c98c1 --- /dev/null +++ b/config_center/nacos/client.go @@ -0,0 +1,234 @@ +/* + * 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 ( + "strconv" + "strings" + "sync" + "time" +) + +import ( + "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/clients/config_client" + nacosconst "github.com/nacos-group/nacos-sdk-go/common/constant" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" +) + +const logDir = "logs/nacos/log" + +// NacosClient Nacos client +type NacosClient struct { + name string + NacosAddrs []string + sync.Mutex // for Client + client *config_client.IConfigClient + exit chan struct{} + Timeout time.Duration + once sync.Once + onceClose func() +} + +// Client Get Client +func (n *NacosClient) Client() *config_client.IConfigClient { + return n.client +} + +// SetClient Set client +func (n *NacosClient) SetClient(client *config_client.IConfigClient) { + n.Lock() + n.client = client + n.Unlock() +} + +type option func(*options) + +type options struct { + nacosName string + client *NacosClient +} + +// WithNacosName Set nacos name +func WithNacosName(name string) option { + return func(opt *options) { + opt.nacosName = name + } +} + +// ValidateNacosClient Validate nacos client , if null then create it +func ValidateNacosClient(container nacosClientFacade, opts ...option) error { + if container == nil { + return perrors.Errorf("container can not be null") + } + os := &options{} + for _, opt := range opts { + opt(os) + } + + url := container.GetUrl() + + if container.NacosClient() == nil { + //in dubbo ,every registry only connect one node ,so this is []string{r.Address} + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + logger.Errorf("timeout config %v is invalid ,err is %v", + url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + nacosAddresses := strings.Split(url.Location, ",") + newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout) + if err != nil { + logger.Warnf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", + os.nacosName, url.Location, timeout.String(), err) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + container.SetNacosClient(newClient) + } + + if container.NacosClient().Client() == nil { + svrConfList := []nacosconst.ServerConfig{} + for _, nacosAddr := range container.NacosClient().NacosAddrs { + split := strings.Split(nacosAddr, ":") + port, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + logger.Warnf("nacos addr port parse error ,error message is %v", err) + continue + } + svrconf := nacosconst.ServerConfig{ + IpAddr: split[0], + Port: port, + } + svrConfList = append(svrConfList, svrconf) + } + + client, err := clients.CreateConfigClient(map[string]interface{}{ + "serverConfigs": svrConfList, + "clientConfig": nacosconst.ClientConfig{ + TimeoutMs: uint64(int32(container.NacosClient().Timeout / time.Millisecond)), + ListenInterval: 10000, + NotLoadCacheAtStart: true, + LogDir: logDir, + }, + }) + + container.NacosClient().SetClient(&client) + if err != nil { + logger.Errorf("nacos create config client error:%v", err) + } + } + + return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL) +} + +func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*NacosClient, error) { + var ( + err error + n *NacosClient + ) + + n = &NacosClient{ + name: name, + NacosAddrs: nacosAddrs, + Timeout: timeout, + exit: make(chan struct{}), + onceClose: func() { + close(n.exit) + }, + } + + svrConfList := []nacosconst.ServerConfig{} + for _, nacosAddr := range n.NacosAddrs { + split := strings.Split(nacosAddr, ":") + port, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + logger.Warnf("convert port , source:%s , error:%v ", split[1], err) + continue + } + svrconf := nacosconst.ServerConfig{ + IpAddr: split[0], + Port: port, + } + svrConfList = append(svrConfList, svrconf) + } + client, err := clients.CreateConfigClient(map[string]interface{}{ + "serverConfigs": svrConfList, + "clientConfig": nacosconst.ClientConfig{ + TimeoutMs: uint64(timeout / time.Millisecond), + ListenInterval: 20000, + NotLoadCacheAtStart: true, + LogDir: logDir, + }, + }) + n.SetClient(&client) + if err != nil { + return nil, perrors.WithMessagef(err, "nacos clients.CreateConfigClient(nacosAddrs:%+v)", nacosAddrs) + } + + return n, nil +} + +// Done Get nacos client exit signal +func (n *NacosClient) Done() <-chan struct{} { + return n.exit +} + +func (n *NacosClient) stop() bool { + select { + case <-n.exit: + return true + default: + n.once.Do(n.onceClose) + } + + return false +} + +// NacosClientValid Get nacos client valid status +func (n *NacosClient) NacosClientValid() bool { + select { + case <-n.exit: + return false + default: + } + + valid := true + n.Lock() + if n.Client() == nil { + valid = false + } + n.Unlock() + + return valid +} + +// Close Close nacos client , then set null +func (n *NacosClient) Close() { + if n == nil { + return + } + + n.stop() + n.SetClient(nil) + logger.Warnf("nacosClient{name:%s, nacos addr:%s} exit now.", n.name, n.NacosAddrs) +} diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ef63eeff6ddf4e5cd6fa2ba7da7996b3dbed94ac --- /dev/null +++ b/config_center/nacos/client_test.go @@ -0,0 +1,55 @@ +/* + * 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 ( + "strings" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +func Test_newNacosClient(t *testing.T) { + server := mockCommonNacosServer() + nacosURL := strings.ReplaceAll(server.URL, "http", "registry") + registryUrl, _ := common.NewURL(nacosURL) + c := &nacosDynamicConfiguration{ + url: ®istryUrl, + done: make(chan struct{}), + } + err := ValidateNacosClient(c, WithNacosName(nacosClientName)) + assert.NoError(t, err) + c.wg.Add(1) + go HandleClientRestart(c) + go func() { + // c.client.Close() and <-c.client.Done() have order requirements. + // If c.client.Close() is called first.It is possible that "go HandleClientRestart(c)" + // sets c.client to nil before calling c.client.Done(). + time.Sleep(time.Second) + c.client.Close() + }() + <-c.client.Done() + c.Destroy() +} diff --git a/config_center/nacos/facade.go b/config_center/nacos/facade.go new file mode 100644 index 0000000000000000000000000000000000000000..fc83e14eac7fcc51025b54f6daff2553f309312c --- /dev/null +++ b/config_center/nacos/facade.go @@ -0,0 +1,97 @@ +/* + * 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 ( + "sync" + "time" +) +import ( + "github.com/dubbogo/getty" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" +) + +const ( + connDelay = 3 + maxFailTimes = 15 +) + +type nacosClientFacade interface { + NacosClient() *NacosClient + SetNacosClient(*NacosClient) + // WaitGroup for wait group control, zk client listener & zk client container + WaitGroup() *sync.WaitGroup + // GetDone For nacos client control RestartCallBack() bool + GetDone() chan struct{} + common.Node +} + +func timeSecondDuration(sec int) time.Duration { + return time.Duration(sec) * time.Second +} + +// HandleClientRestart Restart client handler +func HandleClientRestart(r nacosClientFacade) { + var ( + err error + + failTimes int + ) + + defer r.WaitGroup().Done() +LOOP: + for { + select { + case <-r.GetDone(): + logger.Warnf("(NacosProviderRegistry)reconnectNacosRegistry goroutine exit now...") + break LOOP + // re-register all services + case <-r.NacosClient().Done(): + r.NacosClient().Close() + nacosName := r.NacosClient().name + nacosAddress := r.NacosClient().NacosAddrs + r.SetNacosClient(nil) + + // Connect nacos until success. + failTimes = 0 + for { + select { + case <-r.GetDone(): + logger.Warnf("(NacosProviderRegistry)reconnectZkRegistry goroutine exit now...") + break LOOP + case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * connDelay)): // Prevent crazy reconnection nacos. + } + err = ValidateNacosClient(r, WithNacosName(nacosName)) + logger.Infof("NacosProviderRegistry.validateNacosClient(nacosAddr{%s}) = error{%#v}", + nacosAddress, perrors.WithStack(err)) + if err == nil { + break + } + failTimes++ + if maxFailTimes <= failTimes { + failTimes = maxFailTimes + } + } + } + } +} diff --git a/config_center/nacos/factory.go b/config_center/nacos/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..3de91ea013df0c6bef8d70c741ff840ba3b77572 --- /dev/null +++ b/config_center/nacos/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 nacos + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/parser" +) + +func init() { + extension.SetConfigCenterFactory("nacos", func() config_center.DynamicConfigurationFactory { return &nacosDynamicConfigurationFactory{} }) +} + +type nacosDynamicConfigurationFactory struct { +} + +// GetDynamicConfiguration Get Configuration with URL +func (f *nacosDynamicConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { + dynamicConfiguration, err := newNacosDynamicConfiguration(url) + if err != nil { + return nil, err + } + dynamicConfiguration.SetParser(&parser.DefaultConfigurationParser{}) + return dynamicConfiguration, err + +} diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go new file mode 100644 index 0000000000000000000000000000000000000000..60ab89b003ff62016b9137223425c1051356975f --- /dev/null +++ b/config_center/nacos/impl.go @@ -0,0 +1,166 @@ +/* + * 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 ( + "sync" +) + +import ( + "github.com/nacos-group/nacos-sdk-go/vo" + perrors "github.com/pkg/errors" +) + +import ( + "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/config_center" + "github.com/apache/dubbo-go/config_center/parser" +) + +const nacosClientName = "nacos config_center" + +type nacosDynamicConfiguration struct { + url *common.URL + rootPath string + wg sync.WaitGroup + cltLock sync.Mutex + done chan struct{} + client *NacosClient + keyListeners sync.Map + parser parser.ConfigurationParser +} + +func newNacosDynamicConfiguration(url *common.URL) (*nacosDynamicConfiguration, error) { + c := &nacosDynamicConfiguration{ + rootPath: "/" + url.GetParam(constant.CONFIG_NAMESPACE_KEY, config_center.DEFAULT_GROUP) + "/config", + url: url, + done: make(chan struct{}), + } + err := ValidateNacosClient(c, WithNacosName(nacosClientName)) + if err != nil { + logger.Errorf("nacos client start error ,error message is %v", err) + return nil, err + } + c.wg.Add(1) + go HandleClientRestart(c) + return c, err + +} + +// AddListener Add listener +func (n *nacosDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, opions ...config_center.Option) { + n.addListener(key, listener) +} + +// RemoveListener Remove listener +func (n *nacosDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener, opions ...config_center.Option) { + n.removeListener(key, listener) +} + +//nacos distinguishes configuration files based on group and dataId. defalut group = "dubbo" and dataId = key +func (n *nacosDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) { + return n.GetRule(key, opts...) +} + +// GetInternalProperty Get properties value by key +func (n *nacosDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) { + return n.GetProperties(key, opts...) +} + +// GetRule Get router rule +func (n *nacosDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) { + tmpOpts := &config_center.Options{} + for _, opt := range opts { + opt(tmpOpts) + } + content, err := (*n.client.Client()).GetConfig(vo.ConfigParam{ + DataId: key, + Group: tmpOpts.Group, + }) + if err != nil { + return "", perrors.WithStack(err) + } else { + return string(content), nil + } +} + +// Parser Get Parser +func (n *nacosDynamicConfiguration) Parser() parser.ConfigurationParser { + return n.parser +} + +// SetParser Set Parser +func (n *nacosDynamicConfiguration) SetParser(p parser.ConfigurationParser) { + n.parser = p +} + +// NacosClient Get Nacos Client +func (n *nacosDynamicConfiguration) NacosClient() *NacosClient { + return n.client +} + +// SetNacosClient Set Nacos Client +func (n *nacosDynamicConfiguration) SetNacosClient(client *NacosClient) { + n.cltLock.Lock() + n.client = client + n.cltLock.Unlock() +} + +// WaitGroup for wait group control, zk client listener & zk client container +func (n *nacosDynamicConfiguration) WaitGroup() *sync.WaitGroup { + return &n.wg +} + +// GetDone For nacos client control RestartCallBack() bool +func (n *nacosDynamicConfiguration) GetDone() chan struct{} { + return n.done +} + +// GetUrl Get Url +func (n *nacosDynamicConfiguration) GetUrl() common.URL { + return *n.url +} + +// Destroy Destroy configuration instance +func (n *nacosDynamicConfiguration) Destroy() { + close(n.done) + n.wg.Wait() + n.closeConfigs() +} + +// IsAvailable Get available status +func (n *nacosDynamicConfiguration) IsAvailable() bool { + select { + case <-n.done: + return false + default: + return true + } +} + +func (r *nacosDynamicConfiguration) closeConfigs() { + r.cltLock.Lock() + client := r.client + r.client = nil + r.cltLock.Unlock() + // Close the old client first to close the tmp node + client.Close() + logger.Infof("begin to close provider nacos client") +} diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b4e6f1d0259979eba28dd81e8f480ab4ae03a39f --- /dev/null +++ b/config_center/nacos/impl_test.go @@ -0,0 +1,118 @@ +/* + * 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 ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/parser" +) + +// run mock config server +func runMockConfigServer(configHandler func(http.ResponseWriter, *http.Request), + configListenHandler func(http.ResponseWriter, *http.Request)) *httptest.Server { + uriHandlerMap := make(map[string]func(http.ResponseWriter, *http.Request), 0) + + uriHandlerMap["/nacos/v1/cs/configs"] = configHandler + uriHandlerMap["/nacos/v1/cs/configs/listener"] = configListenHandler + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + uri := r.RequestURI + for path, handler := range uriHandlerMap { + if uri == path { + handler(w, r) + break + } + } + })) + + return ts +} + +func mockCommonNacosServer() *httptest.Server { + return runMockConfigServer(func(writer http.ResponseWriter, request *http.Request) { + data := ` + dubbo.service.com.ikurento.user.UserProvider.cluster=failback + dubbo.service.com.ikurento.user.UserProvider.protocol=myDubbo1 + dubbo.protocols.myDubbo.port=20000 + dubbo.protocols.myDubbo.name=dubbo +` + fmt.Fprintf(writer, "%s", data) + }, func(writer http.ResponseWriter, request *http.Request) { + data := `dubbo.properties%02dubbo%02dubbo.service.com.ikurento.user.UserProvider.cluster=failback` + fmt.Fprintf(writer, "%s", data) + }) +} + +func initNacosData(t *testing.T) (*nacosDynamicConfiguration, error) { + server := mockCommonNacosServer() + nacosURL := strings.ReplaceAll(server.URL, "http", "registry") + regurl, _ := common.NewURL(nacosURL) + nacosConfiguration, err := newNacosDynamicConfiguration(®url) + assert.NoError(t, err) + + nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{}) + + return nacosConfiguration, err +} + +func Test_GetConfig(t *testing.T) { + nacos, err := initNacosData(t) + assert.NoError(t, err) + configs, err := nacos.GetProperties("dubbo.properties", config_center.WithGroup("dubbo")) + _, err = nacos.Parser().Parse(configs) + assert.NoError(t, err) +} + +func Test_AddListener(t *testing.T) { + nacos, err := initNacosData(t) + assert.NoError(t, err) + listener := &mockDataListener{} + time.Sleep(time.Second * 2) + nacos.AddListener("dubbo.properties", listener) + listener.wg.Add(1) + listener.wg.Wait() +} + +func Test_RemoveListener(t *testing.T) { + //TODO not supported in current go_nacos_sdk version +} + +type mockDataListener struct { + wg sync.WaitGroup + event string +} + +func (l *mockDataListener) Process(configType *config_center.ConfigChangeEvent) { + l.wg.Done() + l.event = configType.Key +} diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..25c586586c7202e42ff44d6104e8132961add25a --- /dev/null +++ b/config_center/nacos/listener.go @@ -0,0 +1,62 @@ +/* + * 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 ( + "context" +) + +import ( + "github.com/nacos-group/nacos-sdk-go/vo" +) + +import ( + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/remoting" +) + +func callback(listener config_center.ConfigurationListener, namespace, group, dataId, data string) { + listener.Process(&config_center.ConfigChangeEvent{Key: dataId, Value: data, ConfigType: remoting.EventTypeUpdate}) +} + +func (l *nacosDynamicConfiguration) addListener(key string, listener config_center.ConfigurationListener) { + _, loaded := l.keyListeners.Load(key) + if !loaded { + _, cancel := context.WithCancel(context.Background()) + err := (*l.client.Client()).ListenConfig(vo.ConfigParam{ + DataId: key, + Group: "dubbo", + OnChange: func(namespace, group, dataId, data string) { + go callback(listener, namespace, group, dataId, data) + }, + }) + logger.Errorf("nacos : listen config fail, error:%v ", err) + newListener := make(map[config_center.ConfigurationListener]context.CancelFunc) + newListener[listener] = cancel + l.keyListeners.Store(key, newListener) + } else { + // TODO check goroutine alive, but this version of go_nacos_sdk is not support. + logger.Infof("profile:%s. this profile is already listening", key) + } +} + +func (l *nacosDynamicConfiguration) removeListener(key string, listener config_center.ConfigurationListener) { + // TODO: not supported in current go_nacos_sdk version + logger.Warn("not supported in current go_nacos_sdk version") +} diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index 9aaa1f700f7eb581e952485681d90c051ea516f4..f342dc62e765f8d38c9e64ba3be03f3362f0bf61 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -18,7 +18,6 @@ package parser import ( - "context" "strconv" "strings" ) @@ -110,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 { @@ -139,14 +139,14 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR newUrlStr := urlStr newUrlStr = newUrlStr + "&application" newUrlStr = newUrlStr + v - url, err := common.NewURL(context.Background(), newUrlStr) + url, err := common.NewURL(newUrlStr) if err != nil { return nil, perrors.WithStack(err) } urls = append(urls, &url) } } else { - url, err := common.NewURL(context.Background(), urlStr) + url, err := common.NewURL(urlStr) if err != nil { return nil, perrors.WithStack(err) } @@ -155,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 { @@ -185,7 +186,7 @@ func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, e urlStr = urlStr + constant.APP_DYNAMIC_CONFIGURATORS_CATEGORY urlStr = urlStr + "&configVersion=" urlStr = urlStr + config.ConfigVersion - url, err := common.NewURL(context.Background(), urlStr) + url, err := common.NewURL(urlStr) if err != nil { return nil, perrors.WithStack(err) } @@ -247,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 cca4427587534c1c4d0f9324f932f8018502521c..22e15193cba1b533a2b1b965a44bf9665a6a4e5e 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -17,7 +17,6 @@ package zookeeper import ( - "context" "fmt" "sync" "testing" @@ -35,7 +34,7 @@ import ( ) func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicConfiguration) { - regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111") + regurl, _ := common.NewURL("registry://127.0.0.1:1111") ts, reg, err := newMockZookeeperDynamicConfiguration(®url) reg.SetParser(&parser.DefaultConfigurationParser{}) @@ -78,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/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go index 14b9166b0fc486638c77388c76b49423a8d4a83e..f0de24d2a89f35876a32763eeb75495e8919ecd9 100644 --- a/filter/filter_impl/access_log_filter_test.go +++ b/filter/filter_impl/access_log_filter_test.go @@ -37,11 +37,11 @@ import ( func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - url, _ := common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + url, _ := common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) @@ -56,11 +56,11 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - url, _ := common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&accesslog=true&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + url, _ := common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&accesslog=true&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go index 7b355086f9d48b3fb864ed40d1cb5db999543d77..8917e9141cad4f22ea201a9a07c2873b584c1f92 100644 --- a/filter/filter_impl/active_filter_test.go +++ b/filter/filter_impl/active_filter_test.go @@ -21,7 +21,7 @@ import ( func TestActiveFilter_Invoke(t *testing.T) { invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0)) - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") filter := ActiveFilter{} ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -39,7 +39,7 @@ func TestActiveFilter_OnResponse(t *testing.T) { invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ dubboInvokeStartTime: strconv.FormatInt(c-int64(elapsed), 10), }) - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") filter := ActiveFilter{} ctrl := gomock.NewController(t) defer ctrl.Finish() 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 c90a769bcbc32e1f685404e3bfac54e56de83b90..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 ( @@ -19,7 +36,7 @@ import ( ) func TestConsumerSignFilter_Invoke(t *testing.T) { - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") url.SetParam(constant.SECRET_ACCESS_KEY_KEY, "sk") url.SetParam(constant.ACCESS_KEY_ID_KEY, "ak") inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, nil) 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 f1e014b5c6ae64efc662d763614e0dd4940edd86..5b107b5960ff5adc383d52aa5e393d9fc6e71d14 100644 --- a/filter/filter_impl/auth/default_authenticator_test.go +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -1,7 +1,23 @@ +/* + * 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" "fmt" "net/url" "strconv" @@ -22,7 +38,7 @@ import ( func TestDefaultAuthenticator_Authenticate(t *testing.T) { secret := "dubbo-sk" access := "dubbo-ak" - testurl, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "true") testurl.SetParam(constant.ACCESS_KEY_ID_KEY, access) testurl.SetParam(constant.SECRET_ACCESS_KEY_KEY, secret) @@ -58,7 +74,7 @@ func TestDefaultAuthenticator_Authenticate(t *testing.T) { func TestDefaultAuthenticator_Sign(t *testing.T) { authenticator := &DefaultAuthenticator{} - testurl, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?application=test&interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?application=test&interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.ACCESS_KEY_ID_KEY, "akey") testurl.SetParam(constant.SECRET_ACCESS_KEY_KEY, "skey") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "false") @@ -101,7 +117,7 @@ func Test_getAccessKeyPairFailed(t *testing.T) { } func Test_getSignatureWithinParams(t *testing.T) { - testurl, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "true") inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ "": "", @@ -117,7 +133,7 @@ func Test_getSignatureWithinParams(t *testing.T) { } func Test_getSignature(t *testing.T) { - testurl, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "false") inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, nil) secret := "dubbo" 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 7552a4aa0447e18b40d8160b895d3ab65ee5edb1..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 ( @@ -23,7 +40,7 @@ import ( func TestProviderAuthFilter_Invoke(t *testing.T) { secret := "dubbo-sk" access := "dubbo-ak" - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") url.SetParam(constant.ACCESS_KEY_ID_KEY, access) url.SetParam(constant.SECRET_ACCESS_KEY_KEY, secret) parmas := []interface{}{ 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/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go index 24ed3b95fcab6111f5c432a12c41dd0b60b4a5a2..37c6af7450a75449fce51182684be2f619eda9d8 100644 --- a/filter/filter_impl/generic_service_filter_test.go +++ b/filter/filter_impl/generic_service_filter_test.go @@ -99,7 +99,7 @@ func TestGenericServiceFilter_Invoke(t *testing.T) { _, _ = common.ServiceMap.Register("testprotocol", s) rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil) filter := GetGenericServiceFilter() - url, _ := common.NewURL(context.Background(), "testprotocol://127.0.0.1:20000/com.test.Path") + url, _ := common.NewURL("testprotocol://127.0.0.1:20000/com.test.Path") result := filter.Invoke(context.Background(), &proxy_factory.ProxyInvoker{BaseInvoker: *protocol.NewBaseInvoker(url)}, rpcInvocation) assert.NotNil(t, result) assert.Nil(t, result.Error()) 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/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go index 83697f0f29bc699eb005b72192ae8eb87d6a730f..709404a2af4f4df0dbf625dbbbd673e34975c0db 100644 --- a/filter/filter_impl/metrics_filter_test.go +++ b/filter/filter_impl/metrics_filter_test.go @@ -49,11 +49,11 @@ func TestMetricsFilter_Invoke(t *testing.T) { instance := extension.GetFilter(metricFilterName) - url, _ := common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + url, _ := common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go index c6d6673f3a45e7d73ffd71373b5a7a2860d36a52..a51692dddcc3400032650f4953eb1e28fb047709 100644 --- a/filter/filter_impl/tracing_filter_test.go +++ b/filter/filter_impl/tracing_filter_test.go @@ -37,11 +37,11 @@ import ( ) func TestTracingFilter_Invoke(t *testing.T) { - url, _ := common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + url, _ := common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) diff --git a/go.mod b/go.mod index 44eb8c4b99809af25906e851aca907a15df67710..8d6647e105753f8adb98b46552ac6526988cc9d0 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/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect @@ -43,7 +43,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 b0799377a7ac352cac7b2ed1d9466372c51a4cf7..ef5649e8c398f01adf18c3eb162bd6f3f9c78bdc 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= @@ -450,6 +450,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= @@ -492,7 +494,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/metrics/prometheus/reporter_test.go b/metrics/prometheus/reporter_test.go index d1741d16d03f09ffc19b227e6a405f60bf306f9b..0cb7d09a2c8e71fb88b54789c8eb3ee2cf967fbf 100644 --- a/metrics/prometheus/reporter_test.go +++ b/metrics/prometheus/reporter_test.go @@ -35,11 +35,11 @@ import ( func TestPrometheusReporter_Report(t *testing.T) { reporter := extension.GetMetricReporter(reporterName) - url, _ := common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + url, _ := common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) @@ -51,21 +51,21 @@ func TestPrometheusReporter_Report(t *testing.T) { reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) // consumer side - url, _ = common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=0&retries=&"+ + url, _ = common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=0&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker = protocol.NewBaseInvoker(url) reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) // invalid role - url, _ = common.NewURL(context.Background(), - "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ - "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ - "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ - "BDTService&organization=ikurento.com&owner=ZX®istry.role=9&retries=&"+ + url, _ = common.NewURL( + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=" + + "BDTService&organization=ikurento.com&owner=ZX®istry.role=9&retries=&" + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker = protocol.NewBaseInvoker(url) reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 3923b7e4e7e543f4c60a89aaebf67f6238916722..5ec7db51af0ddfa6e49d3c65910355f0bf2de414 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -18,15 +18,16 @@ package dubbo import ( + "math/rand" "strings" "sync" "time" ) import ( - "github.com/apache/dubbo-go-hessian2" + hessian "github.com/apache/dubbo-go-hessian2" "github.com/dubbogo/getty" - "github.com/dubbogo/gost/sync" + gxsync "github.com/dubbogo/gost/sync" perrors "github.com/pkg/errors" "go.uber.org/atomic" "gopkg.in/yaml.v2" @@ -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 @@ -274,8 +284,8 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac select { case <-getty.GetTimeWheel().After(c.opts.RequestTimeout): - err = errClientReadTimeout c.removePendingResponse(SequenceType(rsp.seq)) + return perrors.WithStack(errClientReadTimeout) case <-rsp.done: err = rsp.err } diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go index 3f8a8ee98c3b2d8b87e2d5469a18d1792578d1d6..1e0a73fac1a6cf6d4d102e5f4f6f1ba60fc4102a 100644 --- a/protocol/dubbo/client_test.go +++ b/protocol/dubbo/client_test.go @@ -210,10 +210,10 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { // Export proto := GetProtocol() - url, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") assert.NoError(t, err) proto.Export(&proxy_factory.ProxyInvoker{ 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 67d1d1e7f1710aef71f63063374a848e2981b828..09c3725710d2a0b821d8e641b0cb7b367189c244 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -21,6 +21,8 @@ import ( "context" "strconv" "sync" + "sync/atomic" + "time" ) import ( @@ -38,7 +40,8 @@ import ( var ( // ErrNoReply ... - ErrNoReply = perrors.New("request need @response") + ErrNoReply = perrors.New("request need @response") + ErrDestroyedInvoker = perrors.New("request Destroyed invoker") ) var ( @@ -50,6 +53,8 @@ type DubboInvoker struct { protocol.BaseInvoker client *Client quitOnce sync.Once + // Used to record the number of requests. -1 represent this DubboInvoker is destroyed + reqNum int64 } // NewDubboInvoker ... @@ -57,6 +62,7 @@ func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, + reqNum: 0, } } @@ -66,6 +72,15 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati err error result protocol.RPCResult ) + if di.reqNum < 0 { + // Generally, the case will not happen, because the invoker has been removed + // from the invoker list before destroy,so no new request will enter the destroyed invoker + logger.Warnf("this dubboInvoker is destroyed") + result.Err = ErrDestroyedInvoker + return &result + } + atomic.AddInt64(&(di.reqNum), 1) + defer atomic.AddInt64(&(di.reqNum), -1) inv := invocation.(*invocation_impl.RPCInvocation) for _, k := range attachmentKey { @@ -110,11 +125,21 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati // Destroy ... func (di *DubboInvoker) Destroy() { di.quitOnce.Do(func() { - di.BaseInvoker.Destroy() - - if di.client != nil { - di.client.Close() + for { + if di.reqNum == 0 { + di.reqNum = -1 + logger.Infof("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key()) + di.BaseInvoker.Destroy() + if di.client != nil { + di.client.Close() + di.client = nil + } + break + } + logger.Warnf("DubboInvoker is to be destroyed, wait {%v} req end,url:{%s}", di.reqNum, di.GetUrl().Key()) + time.Sleep(1 * time.Second) } + }) } diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go index a6b0bc1df3cf2eb46e07c9dab149d04f62f78012..14f6868ad4a7f2bf6f549a2fbbce8234cbb4aa12 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -18,7 +18,6 @@ package dubbo import ( - "context" "testing" ) @@ -36,10 +35,10 @@ func TestDubboProtocol_Export(t *testing.T) { // Export proto := GetProtocol() srvConf = &ServerConfig{} - url, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) @@ -49,7 +48,7 @@ func TestDubboProtocol_Export(t *testing.T) { assert.True(t, eq) // second service: the same path and the different version - url2, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ + url2, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ @@ -78,10 +77,10 @@ func TestDubboProtocol_Export(t *testing.T) { func TestDubboProtocol_Refer(t *testing.T) { // Refer proto := GetProtocol() - url, err := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) clientConf = &ClientConfig{} 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 2df1c6935305e0d70635613f509021e5b9203833..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) { @@ -360,6 +361,13 @@ func (p *gettyRPCClientPool) put(conn *gettyRPCClient) { return } + // check whether @conn has existed in p.conns or not. + for i := range p.conns { + if p.conns[i] == conn { + return + } + } + if len(p.conns) >= p.size { // delete @conn from client pool // p.remove(conn) diff --git a/protocol/grpc/client_test.go b/protocol/grpc/client_test.go index 7d96402782999393fc9ddf7b6c058509e342b366..56ec766f70da93bcddbcff13667a34c39deffe06 100644 --- a/protocol/grpc/client_test.go +++ b/protocol/grpc/client_test.go @@ -18,7 +18,6 @@ limitations under the License. package grpc import ( - "context" "reflect" "testing" ) @@ -47,7 +46,7 @@ func TestNewClient(t *testing.T) { go internal.InitGrpcServer() defer internal.ShutdownGrpcServer() - url, err := common.NewURL(context.Background(), "grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") + url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") assert.Nil(t, err) cli := NewClient(url) assert.NotNil(t, cli) diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go index 5d4b97051438f8404cd8fd89bcf73d24e0121868..368c1392ec03af310f93e8fc2173b8354975c99e 100644 --- a/protocol/grpc/grpc_invoker_test.go +++ b/protocol/grpc/grpc_invoker_test.go @@ -37,7 +37,7 @@ func TestInvoke(t *testing.T) { go internal.InitGrpcServer() defer internal.ShutdownGrpcServer() - url, err := common.NewURL(context.Background(), "grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") + url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") assert.Nil(t, err) cli := NewClient(url) diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go index e4629499b73c3fc4116a355bd66f440e95fe5451..d0206a0fd953e40a478c26a2298f4889d8f72771 100644 --- a/protocol/grpc/grpc_protocol_test.go +++ b/protocol/grpc/grpc_protocol_test.go @@ -18,7 +18,6 @@ limitations under the License. package grpc import ( - "context" "testing" "time" ) @@ -38,7 +37,7 @@ func TestGrpcProtocol_Export(t *testing.T) { addService() proto := GetProtocol() - url, err := common.NewURL(context.Background(), "grpc://127.0.0.1:40000/GrpcGreeterImpl?accesslog=&app.version=0.0.1&application=BDTService&bean.name=GrpcGreeterImpl&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown×tamp=1576923717&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100") + url, err := common.NewURL("grpc://127.0.0.1:40000/GrpcGreeterImpl?accesslog=&app.version=0.0.1&application=BDTService&bean.name=GrpcGreeterImpl&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown×tamp=1576923717&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100") assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) time.Sleep(time.Second) @@ -68,7 +67,7 @@ func TestGrpcProtocol_Refer(t *testing.T) { time.Sleep(time.Second) proto := GetProtocol() - url, err := common.NewURL(context.Background(), "grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") + url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") assert.NoError(t, err) invoker := proto.Refer(url) 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/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 12ddb2dbc858aac26fb4243ecc6578b548e3af01..0cb88b36a8f330059906eb70417b6d4841020c38 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -56,10 +56,10 @@ func TestHTTPClient_Call(t *testing.T) { // Export proto := GetProtocol() - url, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") assert.NoError(t, err) proto.Export(&proxy_factory.ProxyInvoker{ diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index 9eed22e67155f1b0915cbb398bcef55962258407..9e08eed2b4c61e686073a9039a605c4f73aa08c5 100644 --- a/protocol/jsonrpc/jsonrpc_invoker_test.go +++ b/protocol/jsonrpc/jsonrpc_invoker_test.go @@ -42,10 +42,10 @@ func TestJsonrpcInvoker_Invoke(t *testing.T) { // Export proto := GetProtocol() - url, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") assert.NoError(t, err) proto.Export(&proxy_factory.ProxyInvoker{ diff --git a/protocol/jsonrpc/jsonrpc_protocol_test.go b/protocol/jsonrpc/jsonrpc_protocol_test.go index 253ab830dd85e5424811b7fd4e7e7e848adad415..c00bed12fe9fbb4937f21810cee548a25e3b1c05 100644 --- a/protocol/jsonrpc/jsonrpc_protocol_test.go +++ b/protocol/jsonrpc/jsonrpc_protocol_test.go @@ -18,7 +18,6 @@ package jsonrpc import ( - "context" "fmt" "strings" "testing" @@ -38,10 +37,10 @@ import ( func TestJsonrpcProtocol_Export(t *testing.T) { // Export proto := GetProtocol() - url, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) @@ -69,10 +68,10 @@ func TestJsonrpcProtocol_Export(t *testing.T) { func TestJsonrpcProtocol_Refer(t *testing.T) { // Refer proto := GetProtocol() - url, err := common.NewURL(context.Background(), "jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+ - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+ + url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) con := config.ConsumerConfig{ 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 ce2b4dc0d0fae2b271dbaeb3fdafab8858a7aa0c..5a07f44eab0db60ba65a155d6c6190ab4ce2d716 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -1,7 +1,6 @@ package protocol import ( - "context" "strconv" "testing" ) @@ -15,9 +14,9 @@ import ( ) func TestBeginCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") BeginCount(url, "test") urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") @@ -29,9 +28,9 @@ func TestBeginCount(t *testing.T) { } func TestEndCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") EndCount(url, "test", 100, true) urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") @@ -42,36 +41,36 @@ func TestEndCount(t *testing.T) { } func TestGetMethodStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetMethodStatus(url, "test") assert.NotNil(t, status) assert.Equal(t, int32(0), status.total) } func TestGetUrlStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) assert.NotNil(t, status) assert.Equal(t, int32(0), status.total) } func Test_beginCount0(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) beginCount0(status) assert.Equal(t, int32(1), status.active) } func Test_All(t *testing.T) { - defer destroy() + defer CleanAllStatus() - url, _ := common.NewURL(context.TODO(), "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") request(url, "test", 100, false, true) urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") @@ -130,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/consul/utils.go b/registry/consul/utils.go index d295f644631ae63b6bdf035f71f5f104a64083e2..05ac5e76e292a2b574bd5e661bbbcca4f419fc22 100644 --- a/registry/consul/utils.go +++ b/registry/consul/utils.go @@ -18,7 +18,6 @@ package consul import ( - "context" "crypto/md5" "encoding/hex" "fmt" @@ -100,7 +99,7 @@ func retrieveURL(service *consul.ServiceEntry) (common.URL, error) { if !ok { return common.URL{}, perrors.New("retrieve url fails with no url key in service meta") } - url1, err := common.NewURL(context.Background(), url) + url1, err := common.NewURL(url) if err != nil { return common.URL{}, perrors.WithStack(err) } diff --git a/registry/directory/directory.go b/registry/directory/directory.go index f9670af7ea566ce35e89d9173b54752fd16f92bf..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 ... @@ -109,7 +112,10 @@ func (dir *registryDirectory) update(res *registry.ServiceEvent) { } func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { - var url *common.URL + var ( + url *common.URL + oldInvoker protocol.Invoker = nil + ) //judge is override or others if res != nil { url = &res.Service @@ -121,15 +127,24 @@ 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) - dir.cacheInvoker(url) + oldInvoker = dir.cacheInvoker(url) case remoting.EventTypeDel: - //dir.cacheService.EventTypeDel(res.Path, dir.serviceTTL) - dir.uncacheInvoker(url) + oldInvoker = dir.uncacheInvoker(url) logger.Infof("selector delete service url{%s}", res.Service) default: return @@ -138,8 +153,14 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { newInvokers := dir.toGroupInvokers() dir.listenerLock.Lock() - defer dir.listenerLock.Unlock() dir.cacheInvokers = newInvokers + dir.listenerLock.Unlock() + // After dir.cacheInvokers is updated,destroy the oldInvoker + // Ensure that no request will enter the oldInvoker + if oldInvoker != nil { + oldInvoker.Destroy() + } + } func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { @@ -170,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)) } } @@ -177,12 +199,18 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { return groupInvokersList } -func (dir *registryDirectory) uncacheInvoker(url *common.URL) { +// uncacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil +func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker { logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", url.Key()) - dir.cacheInvokersMap.Delete(url.Key()) + if cacheInvoker, ok := dir.cacheInvokersMap.Load(url.Key()); ok { + dir.cacheInvokersMap.Delete(url.Key()) + return cacheInvoker.(protocol.Invoker) + } + return nil } -func (dir *registryDirectory) cacheInvoker(url *common.URL) { +// cacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil +func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { dir.overrideUrl(dir.GetDirectoryUrl()) referenceUrl := dir.GetDirectoryUrl().SubURL @@ -193,33 +221,39 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) { } if url == nil { logger.Error("URL is nil ,pls check if service url is subscribe successfully!") - return + return nil } //check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol if url.Protocol == referenceUrl.Protocol || referenceUrl.Protocol == "" { 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) - cacheInvoker.(protocol.Invoker).Destroy() + return cacheInvoker.(protocol.Invoker) } } } + return nil } //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 { @@ -239,10 +273,11 @@ func (dir *registryDirectory) IsAvailable() bool { func (dir *registryDirectory) Destroy() { //TODO:unregister & unsubscribe dir.BaseDirectory.Destroy(func() { - for _, ivk := range dir.cacheInvokers { + invokers := dir.cacheInvokers + dir.cacheInvokers = []protocol.Invoker{} + for _, ivk := range invokers { ivk.Destroy() } - dir.cacheInvokers = []protocol.Invoker{} }) } diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index b3c1d35aaa66b3437ff89807fba2df0a383921cb..0dde44e73c18f65f262e01f499e198995907dece 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -18,7 +18,6 @@ package directory import ( - "context" "net/url" "strconv" "testing" @@ -31,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" @@ -44,6 +45,7 @@ import ( func init() { config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) } + func TestSubscribe(t *testing.T) { registryDirectory, _ := normalRegistryDir() @@ -62,7 +64,7 @@ func TestSubscribe(t *testing.T) { //} func TestSubscribe_InvalidUrl(t *testing.T) { - url, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url, _ := common.NewURL("mock://127.0.0.1:1111") mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) _, err := NewRegistryDirectory(&url, mockRegistry) assert.Error(t, err) @@ -72,8 +74,8 @@ func TestSubscribe_Group(t *testing.T) { extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) extension.SetCluster("mock", cluster_impl.NewMockCluster) - regurl, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") - suburl, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000") + regurl, _ := common.NewURL("mock://127.0.0.1:1111") + suburl, _ := common.NewURL("dubbo://127.0.0.1:20000") suburl.SetParam(constant.CLUSTER_KEY, "mock") regurl.SubURL = &suburl mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) @@ -105,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()) @@ -117,14 +119,15 @@ 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(context.TODO(), "dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", + providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock1"), common.WithParamsValue(constant.GROUP_KEY, "group"), common.WithParamsValue(constant.VERSION_KEY, "1.0.0")) @@ -139,7 +142,7 @@ func Test_MergeProviderUrl(t *testing.T) { func Test_MergeOverrideUrl(t *testing.T) { registryDirectory, mockRegistry := normalRegistryDir(true) - providerUrl, _ := common.NewURL(context.TODO(), "dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", + providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue(constant.GROUP_KEY, "group"), common.WithParamsValue(constant.VERSION_KEY, "1.0.0")) @@ -147,7 +150,7 @@ func Test_MergeOverrideUrl(t *testing.T) { Loop1: for { if len(registryDirectory.cacheInvokers) > 0 { - overrideUrl, _ := common.NewURL(context.TODO(), "override://0.0.0.0:20000/org.apache.dubbo-go.mockService", + overrideUrl, _ := common.NewURL("override://0.0.0.0:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock1"), common.WithParamsValue(constant.GROUP_KEY, "group"), common.WithParamsValue(constant.VERSION_KEY, "1.0.0")) @@ -173,9 +176,8 @@ Loop1: func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockRegistry) { extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) - url, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url, _ := common.NewURL("mock://127.0.0.1:1111") suburl, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue(constant.GROUP_KEY, "group"), diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go index 5ed56f6ccf6968222712491c9c432674a04a6c18..f9b046a2c52814cd4e5ea38f9ea4c58c8bdb5bc4 100644 --- a/registry/etcdv3/listener.go +++ b/registry/etcdv3/listener.go @@ -18,7 +18,6 @@ package etcdv3 import ( - "context" "strings" ) @@ -51,7 +50,7 @@ func (l *dataListener) AddInterestedURL(url *common.URL) { func (l *dataListener) DataChange(eventType remoting.Event) bool { url := eventType.Path[strings.Index(eventType.Path, "/providers/")+len("/providers/"):] - serviceURL, err := common.NewURL(context.Background(), url) + serviceURL, err := common.NewURL(url) if err != nil { logger.Warnf("Listen NewURL(r{%s}) = error{%v}", eventType.Path, err) return false @@ -84,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 } @@ -109,6 +109,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } } } + func (l *configurationListener) Close() { l.registry.WaitGroup().Done() } diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index c064f99c6c4b447a6c81093b87d99e1d1ba6d17a..928e3fa83d4a19869903d3aaee1691c298b031b2 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -18,10 +18,10 @@ package etcdv3 import ( - "context" - "github.com/apache/dubbo-go/config_center" "testing" "time" + + "github.com/apache/dubbo-go/config_center" ) import ( @@ -73,7 +73,7 @@ func (suite *RegistryTestSuite) TestDataChange() { t := suite.T() listener := NewRegistryDataListener(&MockDataListener{}) - url, _ := common.NewURL(context.Background(), "jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") + url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") listener.AddInterestedURL(&url) if !listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) { t.Fatal("data change not ok") diff --git a/registry/etcdv3/registry_test.go b/registry/etcdv3/registry_test.go index 87cf24016bd6c0fd06b084193bbab82db17f40a7..6e26a8f3fcbbf50592520a44b253e5abbaedb061 100644 --- a/registry/etcdv3/registry_test.go +++ b/registry/etcdv3/registry_test.go @@ -18,7 +18,6 @@ package etcdv3 import ( - "context" "strconv" "testing" "time" @@ -35,7 +34,7 @@ import ( func initRegistry(t *testing.T) *etcdV3Registry { - regurl, err := common.NewURL(context.Background(), "registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + regurl, err := common.NewURL("registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) if err != nil { t.Fatal(err) } @@ -55,7 +54,7 @@ func (suite *RegistryTestSuite) TestRegister() { t := suite.T() - url, _ := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) reg := initRegistry(t) err := reg.Register(url) @@ -71,8 +70,8 @@ func (suite *RegistryTestSuite) TestRegister() { func (suite *RegistryTestSuite) TestSubscribe() { t := suite.T() - regurl, _ := common.NewURL(context.Background(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - url, _ := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) reg := initRegistry(t) //provider register @@ -102,7 +101,7 @@ func (suite *RegistryTestSuite) TestSubscribe() { func (suite *RegistryTestSuite) TestConsumerDestory() { t := suite.T() - url, _ := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) reg := initRegistry(t) _, err := reg.DoSubscribe(&url) @@ -122,7 +121,7 @@ func (suite *RegistryTestSuite) TestProviderDestory() { t := suite.T() reg := initRegistry(t) - url, _ := common.NewURL(context.Background(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) err := reg.Register(url) assert.NoError(t, err) diff --git a/registry/nacos/registry_test.go b/registry/nacos/registry_test.go index e6ab693cd3f5432fe30c2b83011cd56e44ac509f..7475b455c0dda09da65012465711ece264bb3dd5 100644 --- a/registry/nacos/registry_test.go +++ b/registry/nacos/registry_test.go @@ -18,7 +18,6 @@ package nacos import ( - "context" "encoding/json" "net/url" "strconv" @@ -36,14 +35,14 @@ import ( ) func TestNacosRegistry_Register(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) urlMap.Set(constant.INTERFACE_KEY, "com.ikurento.user.UserProvider") urlMap.Set(constant.VERSION_KEY, "1.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, err := newNacosRegistry(®url) assert.Nil(t, err) @@ -65,7 +64,7 @@ func TestNacosRegistry_Register(t *testing.T) { } func TestNacosRegistry_Subscribe(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) @@ -73,7 +72,7 @@ func TestNacosRegistry_Subscribe(t *testing.T) { urlMap.Set(constant.VERSION_KEY, "1.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") urlMap.Set(constant.NACOS_PATH_KEY, "") - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, _ := newNacosRegistry(®url) err := reg.Register(url) @@ -103,7 +102,7 @@ func TestNacosRegistry_Subscribe(t *testing.T) { } func TestNacosRegistry_Subscribe_del(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) @@ -111,8 +110,8 @@ func TestNacosRegistry_Subscribe_del(t *testing.T) { urlMap.Set(constant.VERSION_KEY, "2.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") urlMap.Set(constant.NACOS_PATH_KEY, "") - url1, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) - url2, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.2:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + url1, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + url2, _ := common.NewURL("dubbo://127.0.0.2:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, _ := newNacosRegistry(®url) err := reg.Register(url1) @@ -169,7 +168,7 @@ func TestNacosRegistry_Subscribe_del(t *testing.T) { } func TestNacosListener_Close(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) @@ -177,7 +176,7 @@ func TestNacosListener_Close(t *testing.T) { urlMap.Set(constant.VERSION_KEY, "1.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") urlMap.Set(constant.NACOS_PATH_KEY, "") - url1, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider2", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + url1, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider2", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, _ := newNacosRegistry(®url) listener, err := reg.(*nacosRegistry).subscribe(&url1) assert.Nil(t, err) 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 0c19da59df6e4fd2f663f9e8d541165fe26c3ffa..cee2a6a625368f655d1b9bc5fe8cc37031e1aef7 100644 --- a/registry/protocol/protocol_test.go +++ b/registry/protocol/protocol_test.go @@ -18,7 +18,6 @@ package protocol import ( - "context" "testing" "time" ) @@ -45,15 +44,15 @@ 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) extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) extension.SetCluster("mock", cluster.NewMockCluster) - url, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url, _ := common.NewURL("mock://127.0.0.1:1111") suburl, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000//", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) @@ -76,9 +75,8 @@ func TestRefer(t *testing.T) { func TestMultiRegRefer(t *testing.T) { regProtocol := newRegistryProtocol() referNormal(t, regProtocol) - url2, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:2222") + url2, _ := common.NewURL("mock://127.0.0.1:2222") suburl2, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000//", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) @@ -98,9 +96,8 @@ func TestOneRegRefer(t *testing.T) { regProtocol := newRegistryProtocol() referNormal(t, regProtocol) - url2, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url2, _ := common.NewURL("mock://127.0.0.1:1111") suburl2, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000//", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) @@ -120,9 +117,8 @@ func exporterNormal(t *testing.T, regProtocol *registryProtocol) *common.URL { extension.SetProtocol("registry", GetProtocol) extension.SetRegistry("mock", registry.NewMockRegistry) extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) - url, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url, _ := common.NewURL("mock://127.0.0.1:1111") suburl, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue(constant.GROUP_KEY, "group"), @@ -148,9 +144,8 @@ func TestMultiRegAndMultiProtoExporter(t *testing.T) { regProtocol := newRegistryProtocol() exporterNormal(t, regProtocol) - url2, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:2222") + url2, _ := common.NewURL("mock://127.0.0.1:2222") suburl2, _ := common.NewURL( - context.TODO(), "jsonrpc://127.0.0.1:20000//", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) @@ -178,9 +173,8 @@ func TestOneRegAndProtoExporter(t *testing.T) { regProtocol := newRegistryProtocol() exporterNormal(t, regProtocol) - url2, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + url2, _ := common.NewURL("mock://127.0.0.1:1111") suburl2, _ := common.NewURL( - context.TODO(), "dubbo://127.0.0.1:20000/org.apache.dubbo-go.mockService", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue(constant.GROUP_KEY, "group"), @@ -242,7 +236,6 @@ func TestExportWithOverrideListener(t *testing.T) { return } overrideUrl, _ := common.NewURL( - context.Background(), "override://0:0:0:0/org.apache.dubbo-go.mockService?cluster=mock1&&group=group&&version=1.0.0", ) event := ®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: overrideUrl} @@ -256,7 +249,7 @@ func TestExportWithOverrideListener(t *testing.T) { func TestExportWithServiceConfig(t *testing.T) { extension.SetDefaultConfigurator(configurator.NewMockConfigurator) - ccUrl, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + ccUrl, _ := common.NewURL("mock://127.0.0.1:1111") dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(&ccUrl) common_cfg.GetEnvInstance().SetDynamicConfiguration(dc) regProtocol := newRegistryProtocol() @@ -275,7 +268,7 @@ func TestExportWithServiceConfig(t *testing.T) { func TestExportWithApplicationConfig(t *testing.T) { extension.SetDefaultConfigurator(configurator.NewMockConfigurator) - ccUrl, _ := common.NewURL(context.TODO(), "mock://127.0.0.1:1111") + ccUrl, _ := common.NewURL("mock://127.0.0.1:1111") dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(&ccUrl) common_cfg.GetEnvInstance().SetDynamicConfiguration(dc) regProtocol := newRegistryProtocol() diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index e8952432645eb06a4432eedace5c39367a9f1b7b..fe8e42db9f39190e34142149a6b67c9638a84ed2 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -18,7 +18,6 @@ package zookeeper import ( - "context" "strings" "sync" ) @@ -57,11 +56,11 @@ 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/"):] - serviceURL, err := common.NewURL(context.TODO(), url) + serviceURL, err := common.NewURL(url) if err != nil { logger.Errorf("Listen NewURL(r{%s}) = error{%v} eventType.Path={%v}", url, err, eventType.Path) return false diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go index 615a4bb16a7c5cc3f3e968db24e6bd05ecfdae2a..1a76b29a6f64e0329b289ce50218032a25f6f5cd 100644 --- a/registry/zookeeper/listener_test.go +++ b/registry/zookeeper/listener_test.go @@ -18,7 +18,6 @@ package zookeeper import ( - "context" "testing" ) @@ -34,7 +33,7 @@ import ( func Test_DataChange(t *testing.T) { listener := NewRegistryDataListener(&MockDataListener{}) - url, _ := common.NewURL(context.TODO(), "jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") + url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") listener.AddInterestedURL(&url) int := listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) assert.Equal(t, true, int) 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/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go index 5e5189c277d48f046c6bfa830d45e13d65ca5a01..0d7623ca12a9b4e49f84ec988c796f2e913d537f 100644 --- a/registry/zookeeper/registry_test.go +++ b/registry/zookeeper/registry_test.go @@ -18,7 +18,6 @@ package zookeeper import ( - "context" "strconv" "testing" "time" @@ -35,8 +34,8 @@ import ( ) func Test_Register(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"})) ts, reg, _ := newMockZkRegistry(®url) defer ts.Stop() @@ -47,8 +46,8 @@ func Test_Register(t *testing.T) { } func Test_Subscribe(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) ts, reg, _ := newMockZkRegistry(®url) //provider register @@ -76,8 +75,8 @@ func Test_Subscribe(t *testing.T) { } func Test_ConsumerDestory(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))) - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) ts, reg, err := newMockZkRegistry(®url) defer ts.Stop() @@ -96,8 +95,8 @@ func Test_ConsumerDestory(t *testing.T) { } func Test_ProviderDestory(t *testing.T) { - regurl, _ := common.NewURL(context.TODO(), "registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - url, _ := common.NewURL(context.TODO(), "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) ts, reg, err := newMockZkRegistry(®url) defer ts.Stop() 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 175d758b76bd8aede8fa0d63c92003dfc6b4f35c..a41f6cd3230700332519ce1c2d3489bfcc4b6ef0 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -18,7 +18,6 @@ package zookeeper import ( - "context" "sync" "testing" "time" @@ -71,6 +70,7 @@ func (r *mockFacade) Destroy() { func (r *mockFacade) RestartCallBack() bool { return true } + func (r *mockFacade) IsAvailable() bool { return true } @@ -79,7 +79,7 @@ func Test_Facade(t *testing.T) { ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) defer ts.Stop() - url, _ := common.NewURL(context.Background(), "mock://127.0.0.1") + url, _ := common.NewURL("mock://127.0.0.1") mock := &mockFacade{client: z, URL: &url} go HandleClientRestart(mock) states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 4493c06dc3f13d59b9388268613fe9e08a14033e..77aa05ee9eada327475fa5bf86c7af2c65de0ef2 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -19,6 +19,7 @@ package zookeeper import ( "path" + "strings" "sync" "time" ) @@ -30,6 +31,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/remoting" ) @@ -243,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 } @@ -256,10 +258,14 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi }(dubboPath, listener) //listen sub path recursive - go func(zkPath string, listener remoting.DataListener) { - l.listenDirEvent(zkPath, listener) - logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) - }(dubboPath, listener) + //if zkPath is end of "providers/ & consumers/" we do not listen children dir + if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) == -1 && + strings.LastIndex(zkPath, constant.CONSUMER_CATEGORY) == -1 { + go func(zkPath string, listener remoting.DataListener) { + l.listenDirEvent(zkPath, listener) + logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) + }(dubboPath, listener) + } } select { case zkEvent = <-childEventCh: 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