diff --git a/.gitignore b/.gitignore index f369c2833aeacbff3aa85a6cd1cdc25520928209..568e9f24541dd6f02dd8670436fd48db481b7f21 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ coverage.txt remoting/zookeeper/zookeeper-4unittest/ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ +registry/consul/agent* +config_center/apollo/mockDubbog.properties.json diff --git a/.travis.yml b/.travis.yml index ea6b2035584ba07ae7caa709f83be59cf20c730e..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,11 +14,11 @@ 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: - bash <(curl -s https://codecov.io/bash) + +notifications: + webhooks: https://oapi.dingtalk.com/robot/send?access_token=f5d6237f2c79db584e75604f7f88db1ce1673c8c0e98451217b28fde791e1d4f \ No newline at end of file diff --git a/CHANGE.md b/CHANGE.md index 947a695ca854fe8c3d91d8ea989b52dcddbe1523..9864601a3746031c7c3f31afabc62fb56a48eedb 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,4 +1,76 @@ # 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) + +### 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) + +### 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* [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.2.0 @@ -42,23 +114,23 @@ ### 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/LICENSE b/LICENSE index e76f9d9dd705daad997776153b1060f5bf8c2a1d..75b52484ea471f882c29e02693b4f02dba175b5e 100644 --- a/LICENSE +++ b/LICENSE @@ -176,6 +176,19 @@ END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + 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 diff --git a/NOTICE b/NOTICE index a9bd809c5c43a9d88a773b5f0c421b252abf38de..d7aa899d1cef0fba67826bebd0d587e9cc17ba5d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Dubbo Go -Copyright 2018-2019 The Apache Software Foundation +Copyright 2018-2020 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 6ff358d9d603cb25833a36afb774cc46595e63f5..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,6 +14,12 @@ Apache License, Version 2.0 ## Release note ## +[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.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## Project Architecture ## @@ -27,39 +34,84 @@ If you wanna know more about dubbo-go, please visit this reference [Project Arch Finished List: -- Role: Consumer, Provider -- Transport: HTTP, TCP -- Codec: JsonRPC v2, Hessian v2 -- 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 -- Cluster Strategy: Failover/[Failfast](https://github.com/apache/dubbo-go/pull/140)/[Failsafe/Failback](https://github.com/apache/dubbo-go/pull/136)/[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) -- Filter: Echo Health Check/[Circuit break and service downgrade](https://github.com/apache/dubbo-go/pull/133)/[TokenFilter](https://github.com/apache/dubbo-go/pull/202)/[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) -- Other feature: [generic invoke](https://github.com/apache/dubbo-go/pull/122)/start check/connecting certain provider/multi-protocols/multi-registries/multi-versions/service group +- Role + * Consumer + * Provider + +- Transport + * HTTP + * TCP + +- 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) + +- Cluster Strategy + * Failover + * [Failfast](https://github.com/apache/dubbo-go/pull/140) + * [Failsafe/Failback](https://github.com/apache/dubbo-go/pull/136) + * [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) + * [TokenFilter](https://github.com/apache/dubbo-go/pull/202) + * [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) + * [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 + * multi-protocols + * multi-registries + * multi-versions + * service group Working List: -- Load Balance: ConsistentHash - Registry: k8s -- Configure Center: apollo - Metadata Center (dubbo v2.7.x) -- Metrics: Promethus(dubbo v2.7.x) - -Todo List: - -- Registry: kubernetes -- Routing: istio -- tracing (dubbo ecosystem) +- 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 -TODO +https://dubbogo.github.io/dubbo-go-website (**Improving**) ## Quick Start -[dubbogo-samples](https://github.com/dubbogo/dubbogo-samples) shows how to use dubbo-go. Please read the [dubbogo-samples/README.md](https://github.com/dubbogo/dubbogo-samples/blob/master/README.md) carefully to learn how to dispose the configuration and compile the program. +[dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) shows how to use dubbo-go. Please read the [dubbo-samples/golang/README.md](https://github.com/dubbogo/dubbo-samples/blob/master/golang/README.md) carefully to learn how to dispose the configuration and compile the program. ## Running unit tests @@ -75,7 +127,7 @@ Windows before_ut.bat ``` -# Run +### Run ```bash go test ./... @@ -83,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). @@ -97,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 7a7b061960e8dcc49f1da0ba6490ca8bbbf09e22..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,6 +13,12 @@ Apache License, Version 2.0 ## 鍙戝竷鏃ュ織 ## +[v1.3.0 - 2020骞�3鏈�1鏃(https://github.com/apache/dubbo-go/releases/tag/v1.3.0) + +[v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0) + +[v1.1.0 - 2019骞�9鏈�7鏃� 鎹愮尞缁橝pache涔嬪悗鐨勭涓€娆elease](https://github.com/apache/dubbo-go/releases/tag/v1.1.0) + [v1.0.0 - 2019骞�5鏈�29鏃� 鍏煎dubbo v2.6.5 鐗堟湰](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## 宸ョ▼鏋舵瀯 ## @@ -26,40 +33,83 @@ Apache License, Version 2.0 瀹炵幇鍒楄〃: -- 瑙掕壊绔�: Consumer, Provider -- 浼犺緭鍗忚: HTTP, TCP -- 搴忓垪鍖栧崗璁�: JsonRPC v2, Hessian v2 -- 娉ㄥ唽涓績: 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) -- 鍔ㄦ€侀厤缃腑蹇冧笌鏈嶅姟娌荤悊閰嶇疆鍣紙config center锛�: Zookeeper -- 闆嗙兢绛栫暐: Failover/[Failfast](https://github.com/apache/dubbo-go/pull/140)/[Failsafe/Failback](https://github.com/apache/dubbo-go/pull/136)/[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) -- 杩囨护鍣�: Echo Health Check/[鏈嶅姟鐔旀柇&闄嶇骇](https://github.com/apache/dubbo-go/pull/133)/[TokenFilter](https://github.com/apache/dubbo-go/pull/202)/[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)/鍚姩鏃舵鏌�/鏈嶅姟鐩磋繛/澶氭湇鍔″崗璁�/澶氭敞鍐屼腑蹇�/澶氭湇鍔$増鏈�/鏈嶅姟鍒嗙粍 +- 瑙掕壊绔� + * Consumer + * Provider + +- 浼犺緭鍗忚 + * HTTP + * TCP + +- 搴忓垪鍖栧崗璁� + * JsonRPC V2 + * Hessian V2 + +- 鍗忚 + * 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) + * [Failsafe/Failback](https://github.com/apache/dubbo-go/pull/136) + * [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) + * [TokenFilter](https://github.com/apache/dubbo-go/pull/202) + * [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) + +- 鍏朵粬鍔熻兘鏀寔: + * 鍚姩鏃舵鏌� + * 鏈嶅姟鐩磋繛 + * 澶氭湇鍔″崗璁� + * 澶氭敞鍐屼腑蹇� + * 澶氭湇鍔$増鏈� + * 鏈嶅姟鍒嗙粍 寮€鍙戜腑鍒楄〃: -- 闆嗙兢绛栫暐: Forking -- 璐熻浇鍧囪 绛栫暐: ConsistentHash - 娉ㄥ唽涓績: k8s -- 閰嶇疆涓績: apollo - 鍏冩暟鎹腑蹇� (dubbo v2.7.x) -- Metrics: Promethus(dubbo v2.7.x) +- Metrics: Opentracing/Promethus(dubbo v2.7.x) -浠诲姟鍒楄〃: +浣犲彲浠ラ€氳繃璁块棶 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 鐭ラ亾鏇村鍏充簬 dubbo-go 鐨勪俊鎭€� -- 娉ㄥ唽涓績: kubernetes -- Routing: istio -- tracing (dubbo ecosystem) - -浣犲彲浠ラ€氳繃璁块棶 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 鐭ラ亾鏇村鍏充簬 dubbo-go 鐨勪俊鎭� + ## 鏂囨。 -TODO +https://dubbogo.github.io/dubbo-go-website (**瀹屽杽涓�**) ## 蹇€熷紑濮� ## -[dubbogo-samples](https://github.com/dubbogo/dubbogo-samples)杩欎釜椤圭洰鐨勪簨渚嬪睍绀轰簡濡備綍浣跨敤 dubbo-go 銆傝浠旂粏闃呰 [dubbogo-samples/README.md](https://github.com/dubbogo/dubbogo-samples/blob/master/README.md) 瀛︿範濡備綍澶勭悊閰嶇疆骞剁紪璇戠▼搴忋€� +[dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples)杩欎釜椤圭洰鐨勪簨渚嬪睍绀轰簡濡備綍浣跨敤 dubbo-go 銆傝浠旂粏闃呰 [dubbo-samples/golang/README.md](https://github.com/dubbogo/dubbo-samples/blob/master/golang/README.md) 瀛︿範濡備綍澶勭悊閰嶇疆骞剁紪璇戠▼搴忋€� ## 杩愯鍗曟祴 @@ -75,7 +125,7 @@ Windows before_ut.bat ``` -# 鎵ц +### 鎵ц ```bash go test ./... @@ -83,6 +133,10 @@ go test ./... go test ./... -coverprofile=coverage.txt -covermode=atomic ``` +## 缂栬瘧 + +璇风Щ姝� [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) + ## 濡備綍璐$尞 濡傛灉鎮ㄦ効鎰忕粰 [Apache/dubbo-go](https://github.com/apache/dubbo-go) 璐$尞浠g爜鎴栬€呮枃妗o紝鎴戜滑閮界儹鐑堟杩庛€傚叿浣撹鍙傝€� [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md)銆� @@ -98,8 +152,5 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic 鑻ヤ綘姝e湪浣跨敤 [apache/dubbo-go](github.com/apache/dubbo-go) 涓旇涓哄叾鏈夌敤鎴栬€呭悜瀵瑰叾鍋氭敼杩涳紝璇峰繚鍒楄吹鍙镐俊鎭簬 [鐢ㄦ埛鍒楄〃](https://github.com/apache/dubbo-go/issues/2)锛屼互渚挎垜浠煡鏅撲箣銆�  - -## Stargazers - -[](https://starchart.cc/apache/dubbo-go) - + + diff --git a/before_ut.bat b/before_ut.bat index 5296d0f8769b7b9f521f82e68bf3b10f4b5d16b4..abe7fc250ef44bf01396ae20c4dacc9db3f60ce2 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -14,8 +14,24 @@ :: See the License for the specific language governing permissions and :: limitations under the License. -set zkJar=zookeeper-3.4.9-fatjar.jar -md remoting\zookeeper\zookeeper-4unittest\contrib\fatjar config_center\zookeeper\zookeeper-4unittest\contrib\fatjar registry\zookeeper\zookeeper-4unittest\contrib\fatjar -curl -L https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJar% -o remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar% -xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/" -xcopy /f "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file +set zkJarName="zookeeper-3.4.9-fatjar.jar" +set remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJarName%" +set zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" +set zkJar="%zkJarPath%/%zkJarName%" + +if not exist "%zkJar%" ( + md %zkJarPath% + curl -L %remoteJarUrl% -o %zkJar% +) + +md config_center\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/" + +md registry\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\chain\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" + +md cluster\router\condition\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file diff --git a/before_ut.sh b/before_ut.sh old mode 100644 new mode 100755 index 323173bcc64c3cbe9916747e10dd3ea8538457ea..7ee92e57a26cbdbb1d1a0b3e792726ad5e1954f8 --- a/before_ut.sh +++ b/before_ut.sh @@ -14,8 +14,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +zkJarName="zookeeper-3.4.9-fatjar.jar" +remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${zkJarName}" +zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" +zkJar="${zkJarPath}/${zkJarName}" -mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar -wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ \ No newline at end of file +if [ ! -f "${zkJar}" ]; then + mkdir -p ${zkJarPath} + wget -P "${zkJarPath}" ${remoteJarUrl} +fi + +mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar + +mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/cluster.go b/cluster/cluster.go index d89df5a21954d63552b857d988fa9a9e7d1fcfb5..617ce5ebf0fa7b5dc7f6047caacec9865aa6960f 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -21,6 +21,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// Cluster ... type Cluster interface { Join(Directory) protocol.Invoker } diff --git a/cluster/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go index 7e748cd938319ff437bb3fb6c7945b857d316069..2ad140b93e15b97d1517119b07b1080a68a0503f 100644 --- a/cluster/cluster_impl/available_cluster.go +++ b/cluster/cluster_impl/available_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(available, NewAvailableCluster) } +// NewAvailableCluster ... func NewAvailableCluster() cluster.Cluster { return &availableCluster{} } diff --git a/cluster/cluster_impl/available_cluster_invoker.go b/cluster/cluster_impl/available_cluster_invoker.go index c59c0702c216fe5c58d190a023322aaa00ac9c17..6f6d2dffbbbf2f6c758097b11713ae0c1b6bd387 100644 --- a/cluster/cluster_impl/available_cluster_invoker.go +++ b/cluster/cluster_impl/available_cluster_invoker.go @@ -18,6 +18,7 @@ limitations under the License. package cluster_impl import ( + "context" "fmt" ) @@ -34,13 +35,14 @@ type availableClusterInvoker struct { baseClusterInvoker } +// NewAvailableClusterInvoker ... func NewAvailableClusterInvoker(directory cluster.Directory) protocol.Invoker { return &availableClusterInvoker{ baseClusterInvoker: newBaseClusterInvoker(directory), } } -func (invoker *availableClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *availableClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) if err != nil { @@ -54,7 +56,7 @@ func (invoker *availableClusterInvoker) Invoke(invocation protocol.Invocation) p for _, ivk := range invokers { if ivk.IsAvailable() { - return ivk.Invoke(invocation) + return ivk.Invoke(ctx, invocation) } } return &protocol.RPCResult{Err: errors.New(fmt.Sprintf("no provider available in %v", invokers))} diff --git a/cluster/cluster_impl/available_cluster_invoker_test.go b/cluster/cluster_impl/available_cluster_invoker_test.go index 04032a7f24dec0e73acb15921f753921391f1515..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 { @@ -66,7 +66,7 @@ func TestAvailableClusterInvokerSuccess(t *testing.T) { invoker.EXPECT().IsAvailable().Return(true) invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) } @@ -80,7 +80,7 @@ func TestAvailableClusterInvokerNoAvail(t *testing.T) { invoker.EXPECT().IsAvailable().Return(false) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.TODO(), &invocation.RPCInvocation{}) assert.NotNil(t, result.Error()) assert.True(t, strings.Contains(result.Error().Error(), "no provider available")) diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index d93e9a6a98a8cbf7ee2cb97abd0248353e0c3154..12799994125c4bf5d968dfc811cda374effbf85c 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -35,6 +35,7 @@ type baseClusterInvoker struct { directory cluster.Directory availablecheck bool destroyed *atomic.Bool + stickyInvoker protocol.Invoker } func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { @@ -44,6 +45,7 @@ func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { destroyed: atomic.NewBool(false), } } + func (invoker *baseClusterInvoker) GetUrl() common.URL { return invoker.directory.GetUrl() } @@ -56,7 +58,9 @@ func (invoker *baseClusterInvoker) Destroy() { } func (invoker *baseClusterInvoker) IsAvailable() bool { - //TODO:sticky connection + if invoker.stickyInvoker != nil { + return invoker.stickyInvoker.IsAvailable() + } return invoker.directory.IsAvailable() } @@ -83,15 +87,42 @@ func (invoker *baseClusterInvoker) checkWhetherDestroyed() error { } func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker { - //todo:sticky connect + + var selectedInvoker protocol.Invoker + url := invokers[0].GetUrl() + sticky := url.GetParamBool(constant.STICKY_KEY, false) + //Get the service method sticky config if have + sticky = url.GetMethodParamBool(invocation.MethodName(), constant.STICKY_KEY, sticky) + + if invoker.stickyInvoker != nil && !isInvoked(invoker.stickyInvoker, invokers) { + invoker.stickyInvoker = nil + } + + if sticky && invoker.stickyInvoker != nil && (invoked == nil || !isInvoked(invoker.stickyInvoker, invoked)) { + if invoker.availablecheck && invoker.stickyInvoker.IsAvailable() { + return invoker.stickyInvoker + } + } + + selectedInvoker = invoker.doSelectInvoker(lb, invocation, invokers, invoked) + + if sticky { + invoker.stickyInvoker = selectedInvoker + } + return selectedInvoker + +} + +func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker { if len(invokers) == 1 { return invokers[0] } + selectedInvoker := lb.Select(invokers, invocation) //judge to if the selectedInvoker is invoked - if !selectedInvoker.IsAvailable() || !invoker.availablecheck || isInvoked(selectedInvoker, invoked) { + if (!selectedInvoker.IsAvailable() && invoker.availablecheck) || isInvoked(selectedInvoker, invoked) { // do reselect var reslectInvokers []protocol.Invoker @@ -106,13 +137,12 @@ func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation p } if len(reslectInvokers) > 0 { - return lb.Select(reslectInvokers, invocation) + selectedInvoker = lb.Select(reslectInvokers, invocation) } else { return nil } } return selectedInvoker - } func isInvoked(selectedInvoker protocol.Invoker, invoked []protocol.Invoker) bool { diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d074697b85a3cf5b770de90da4847043d98c9df1 --- /dev/null +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -0,0 +1,66 @@ +/* + * 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 cluster_impl + +import ( + "fmt" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/loadbalance" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func Test_StickyNormal(t *testing.T) { + invokers := []protocol.Invoker{} + for i := 0; i < 10; 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)) + } + base := &baseClusterInvoker{} + base.availablecheck = true + invoked := []protocol.Invoker{} + result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + 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(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url.SetParam("sticky", "true") + invokers = append(invokers, NewMockInvoker(url, 1)) + } + base := &baseClusterInvoker{} + base.availablecheck = true + + invoked := []protocol.Invoker{} + result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + invoked = append(invoked, result) + result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + assert.NotEqual(t, result, result1) +} diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index 50aae3cfab8d67570b50dcab4e53bbfad29d6d30..9b27a4ce37bc73e42b55e4e20deb9593fd837444 100644 --- a/cluster/cluster_impl/broadcast_cluster.go +++ b/cluster/cluster_impl/broadcast_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(broadcast, NewBroadcastCluster) } +// NewBroadcastCluster ... func NewBroadcastCluster() cluster.Cluster { return &broadcastCluster{} } diff --git a/cluster/cluster_impl/broadcast_cluster_invoker.go b/cluster/cluster_impl/broadcast_cluster_invoker.go index 238df0acfa7fb946e38bfbfd490bce7c0bb34e60..1b49e9a115252d4eca94bedd557ebcc21fee4cc7 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker.go @@ -17,6 +17,9 @@ limitations under the License. package cluster_impl +import ( + "context" +) import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/logger" @@ -33,7 +36,7 @@ func newBroadcastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -func (invoker *broadcastClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *broadcastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) if err != nil { @@ -46,7 +49,7 @@ func (invoker *broadcastClusterInvoker) Invoke(invocation protocol.Invocation) p var result protocol.Result for _, ivk := range invokers { - result = ivk.Invoke(invocation) + result = ivk.Invoke(ctx, invocation) if result.Error() != nil { logger.Warnf("broadcast invoker invoke err: %v when use invoker: %v\n", result.Error(), ivk) err = result.Error() diff --git a/cluster/cluster_impl/broadcast_cluster_invoker_test.go b/cluster/cluster_impl/broadcast_cluster_invoker_test.go index 565684a8ae25c648ff77aef71d2ced0665202fe7..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 { @@ -74,7 +74,7 @@ func Test_BroadcastInvokeSuccess(t *testing.T) { clusterInvoker := registerBroadcast(t, invokers...) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) } @@ -104,6 +104,6 @@ func Test_BroadcastInvokeFailed(t *testing.T) { clusterInvoker := registerBroadcast(t, invokers...) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockFailedResult.Err, result.Error()) } diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index de22c78e947d0b8124add721ab7ff42efebcdbe4..76573571684c07f63609009f59ab0ac881ae1b50 100644 --- a/cluster/cluster_impl/failback_cluster.go +++ b/cluster/cluster_impl/failback_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(failback, NewFailbackCluster) } +// NewFailbackCluster ... func NewFailbackCluster() cluster.Cluster { return &failbackCluster{} } diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go index c8dbeda09f62e88b51dd4ad2b6b09d5715f0b224..46b0ff634e56c45223a5aeb5566b9b1401518960 100644 --- a/cluster/cluster_impl/failback_cluster_invoker.go +++ b/cluster/cluster_impl/failback_cluster_invoker.go @@ -18,6 +18,7 @@ package cluster_impl import ( + "context" "strconv" "sync" "time" @@ -71,7 +72,7 @@ func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker { return invoker } -func (invoker *failbackClusterInvoker) process() { +func (invoker *failbackClusterInvoker) process(ctx context.Context) { invoker.ticker = time.NewTicker(time.Second * 1) for range invoker.ticker.C { // check each timeout task and re-run @@ -102,7 +103,7 @@ func (invoker *failbackClusterInvoker) process() { retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked) var result protocol.Result - result = retryInvoker.Invoke(retryTask.invocation) + result = retryInvoker.Invoke(ctx, retryTask.invocation) if result.Error() != nil { retryTask.lastInvoker = retryInvoker invoker.checkRetry(retryTask, result.Error()) @@ -126,7 +127,7 @@ func (invoker *failbackClusterInvoker) checkRetry(retryTask *retryTimerTask, err } } -func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) if err != nil { @@ -150,11 +151,11 @@ func (invoker *failbackClusterInvoker) Invoke(invocation protocol.Invocation) pr ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) //DO INVOKE - result = ivk.Invoke(invocation) + result = ivk.Invoke(ctx, invocation) if result.Error() != nil { invoker.once.Do(func() { invoker.taskList = queue.New(invoker.failbackTasks) - go invoker.process() + go invoker.process(ctx) }) taskLen := invoker.taskList.Len() diff --git a/cluster/cluster_impl/failback_cluster_test.go b/cluster/cluster_impl/failback_cluster_test.go index c94347a1251a69a10c0a4d50007ef569bd6dd996..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. @@ -67,12 +67,12 @@ func Test_FailbackSuceess(t *testing.T) { invoker := mock.NewMockInvoker(ctrl) clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) - invoker.EXPECT().GetUrl().Return(failbackUrl).Times(1) + invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) } @@ -102,7 +102,7 @@ func Test_FailbackRetryOneSuccess(t *testing.T) { return mockSuccResult }) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) assert.Equal(t, 0, len(result.Attachments())) @@ -150,7 +150,7 @@ func Test_FailbackRetryFailed(t *testing.T) { } // first call should failed. - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) assert.Equal(t, 0, len(result.Attachments())) @@ -192,7 +192,7 @@ func Test_FailbackRetryFailed10Times(t *testing.T) { }).Times(10) for i := 0; i < 10; i++ { - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) assert.Equal(t, 0, len(result.Attachments())) @@ -222,14 +222,14 @@ func Test_FailbackOutOfLimit(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).Return(mockFailedResult).Times(11) // reached limit - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) assert.Equal(t, 0, len(result.Attachments())) // all will be out of limit for i := 0; i < 10; i++ { - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) assert.Equal(t, 0, len(result.Attachments())) diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go index 6301d945626103226132b433dd21e8647f53a38b..e0b80ded041cd30b379857ff00d307811e53765d 100644 --- a/cluster/cluster_impl/failfast_cluster.go +++ b/cluster/cluster_impl/failfast_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(failfast, NewFailFastCluster) } +// NewFailFastCluster ... func NewFailFastCluster() cluster.Cluster { return &failfastCluster{} } diff --git a/cluster/cluster_impl/failfast_cluster_invoker.go b/cluster/cluster_impl/failfast_cluster_invoker.go index 734ea2c6cb19bf54a338a76a10c9cfcc59d3954b..49e7c7689f5a19a36154e092a6a83cc39da604ba 100644 --- a/cluster/cluster_impl/failfast_cluster_invoker.go +++ b/cluster/cluster_impl/failfast_cluster_invoker.go @@ -17,6 +17,9 @@ limitations under the License. package cluster_impl +import ( + "context" +) import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/protocol" @@ -32,7 +35,7 @@ func newFailFastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -func (invoker *failfastClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *failfastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) if err != nil { @@ -47,5 +50,5 @@ func (invoker *failfastClusterInvoker) Invoke(invocation protocol.Invocation) pr } ivk := invoker.doSelect(loadbalance, invocation, invokers, nil) - return ivk.Invoke(invocation) + return ivk.Invoke(ctx, invocation) } diff --git a/cluster/cluster_impl/failfast_cluster_test.go b/cluster/cluster_impl/failfast_cluster_test.go index 7a19e80ccda15aa13a1c4fcf250e05a6effa7f0b..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. @@ -64,12 +64,12 @@ func Test_FailfastInvokeSuccess(t *testing.T) { invoker := mock.NewMockInvoker(ctrl) clusterInvoker := registerFailfast(t, invoker) - invoker.EXPECT().GetUrl().Return(failfastUrl) + invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes() mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) res := result.Result().(rest) @@ -84,12 +84,12 @@ func Test_FailfastInvokeFail(t *testing.T) { invoker := mock.NewMockInvoker(ctrl) clusterInvoker := registerFailfast(t, invoker) - invoker.EXPECT().GetUrl().Return(failfastUrl) + invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes() mockResult := &protocol.RPCResult{Err: perrors.New("error")} invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NotNil(t, result.Error()) assert.Equal(t, "error", result.Error().Error()) diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index 0f1aa0371f57df5414a04a59e2a6772a4cd382b3..b16be3bafd43c7de8e2fadd109a73a3ea710e225 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(name, NewFailoverCluster) } +// NewFailoverCluster ... func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index dcce7369931a11f31fb6b9e4e1a6c0aa0ec7cdf6..6178a05a1226ba629d2456ad6886b02a26288e45 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -18,6 +18,7 @@ package cluster_impl import ( + "context" "strconv" ) @@ -43,7 +44,7 @@ func newFailoverClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -func (invoker *failoverClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) @@ -95,7 +96,7 @@ func (invoker *failoverClusterInvoker) Invoke(invocation protocol.Invocation) pr } invoked = append(invoked, ivk) //DO INVOKE - result = ivk.Invoke(invocation) + result = ivk.Invoke(ctx, invocation) if result.Error() != nil { providers = append(providers, ivk.GetUrl().Key()) continue diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index 78b799320dfa58d55e531c658ec5eb0e69306cff..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 @@ -77,10 +77,12 @@ type rest struct { success bool } -func (bi *MockInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) protocol.Result { count++ - var success bool - var err error = nil + var ( + success bool + err error + ) if count >= bi.successCount { success = true } else { @@ -105,17 +107,18 @@ 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)) } staticDir := directory.NewStaticDirectory(invokers) clusterInvoker := failoverCluster.Join(staticDir) if len(invocations) > 0 { - return clusterInvoker.Invoke(invocations[0]) + return clusterInvoker.Invoke(context.Background(), invocations[0]) } - return clusterInvoker.Invoke(&invocation.RPCInvocation{}) + return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } + func Test_FailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} result := normalInvoke(t, 3, urlParams) @@ -155,14 +158,14 @@ func Test_FailoverDestroy(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, NewMockInvoker(url, 1)) } staticDir := directory.NewStaticDirectory(invokers) clusterInvoker := failoverCluster.Join(staticDir) assert.Equal(t, true, clusterInvoker.IsAvailable()) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) count = 0 clusterInvoker.Destroy() diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go index 3ff97d25eae80980a90a03e71865bb8f9a63defe..177d24a585b5f72fb0667215beb8d11147cc2922 100644 --- a/cluster/cluster_impl/failsafe_cluster.go +++ b/cluster/cluster_impl/failsafe_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(failsafe, NewFailsafeCluster) } +// NewFailsafeCluster ... func NewFailsafeCluster() cluster.Cluster { return &failsafeCluster{} } diff --git a/cluster/cluster_impl/failsafe_cluster_invoker.go b/cluster/cluster_impl/failsafe_cluster_invoker.go index b95f997fef87cf466f07c4e506e41758e7998e52..4d8fe27719eb71fa287fe4142d8e92ca17acfba4 100644 --- a/cluster/cluster_impl/failsafe_cluster_invoker.go +++ b/cluster/cluster_impl/failsafe_cluster_invoker.go @@ -17,6 +17,9 @@ package cluster_impl +import ( + "context" +) import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" @@ -42,7 +45,7 @@ func newFailsafeClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -func (invoker *failsafeClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) @@ -65,7 +68,7 @@ func (invoker *failsafeClusterInvoker) Invoke(invocation protocol.Invocation) pr ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) //DO INVOKE - result = ivk.Invoke(invocation) + result = ivk.Invoke(ctx, invocation) if result.Error() != nil { // ignore logger.Errorf("Failsafe ignore exception: %v.\n", result.Error().Error()) diff --git a/cluster/cluster_impl/failsafe_cluster_test.go b/cluster/cluster_impl/failsafe_cluster_test.go index 9ee9d9fee31b0cb24d877ab3dc0e24fb552f5f11..2e35de8da91cc78730b6380bf039f0626ca75ec0 100644 --- a/cluster/cluster_impl/failsafe_cluster_test.go +++ b/cluster/cluster_impl/failsafe_cluster_test.go @@ -39,11 +39,11 @@ 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") ) -// register_failsafe register failsafeCluster to cluster extension. -func register_failsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { +// registerFailsafe register failsafeCluster to cluster extension. +func registerFailsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failsafeCluster := NewFailsafeCluster() @@ -62,14 +62,14 @@ func Test_FailSafeInvokeSuccess(t *testing.T) { defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := register_failsafe(t, invoker) + clusterInvoker := registerFailsafe(t, invoker) - invoker.EXPECT().GetUrl().Return(failsafeUrl) + invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes() mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) res := result.Result().(rest) @@ -81,14 +81,14 @@ func Test_FailSafeInvokeFail(t *testing.T) { defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := register_failsafe(t, invoker) + clusterInvoker := registerFailsafe(t, invoker) - invoker.EXPECT().GetUrl().Return(failsafeUrl) + invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes() mockResult := &protocol.RPCResult{Err: perrors.New("error")} invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) assert.Nil(t, result.Result()) diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go index 0a3c2b313ff3c4e89e592af9256fc42713419914..6b0572b15088e86870b3d9fd911a1d0b022378be 100644 --- a/cluster/cluster_impl/forking_cluster.go +++ b/cluster/cluster_impl/forking_cluster.go @@ -31,6 +31,7 @@ func init() { extension.SetCluster(forking, NewForkingCluster) } +// NewForkingCluster ... func NewForkingCluster() cluster.Cluster { return &forkingCluster{} } diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go index d6cf2f4b89ab4f322fa758deecae90c60742ef49..058d7fefd6edf6c43e5eda4b8f2f6a9c161189e2 100644 --- a/cluster/cluster_impl/forking_cluster_invoker.go +++ b/cluster/cluster_impl/forking_cluster_invoker.go @@ -18,7 +18,7 @@ limitations under the License. package cluster_impl import ( - "errors" + "context" "fmt" "time" ) @@ -44,7 +44,8 @@ func newForkingClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -func (invoker *forkingClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { err := invoker.checkWhetherDestroyed() if err != nil { return &protocol.RPCResult{Err: err} @@ -75,7 +76,7 @@ func (invoker *forkingClusterInvoker) Invoke(invocation protocol.Invocation) pro resultQ := queue.New(1) for _, ivk := range selected { go func(k protocol.Invoker) { - result := k.Invoke(invocation) + result := k.Invoke(ctx, invocation) err := resultQ.Put(result) if err != nil { logger.Errorf("resultQ put failed with exception: %v.\n", err) @@ -86,14 +87,18 @@ func (invoker *forkingClusterInvoker) Invoke(invocation protocol.Invocation) pro rsps, err := resultQ.Poll(1, time.Millisecond*time.Duration(timeouts)) if err != nil { return &protocol.RPCResult{ - Err: errors.New(fmt.Sprintf("failed to forking invoke provider %v, but no luck to perform the invocation. Last error is: %s", selected, err.Error()))} + Err: fmt.Errorf("failed to forking invoke provider %v, "+ + "but no luck to perform the invocation. Last error is: %v", selected, err), + } } if len(rsps) == 0 { - return &protocol.RPCResult{Err: errors.New(fmt.Sprintf("failed to forking invoke provider %v, but no resp", selected))} + return &protocol.RPCResult{Err: fmt.Errorf("failed to forking invoke provider %v, but no resp", selected)} } + result, ok := rsps[0].(protocol.Result) if !ok { - return &protocol.RPCResult{Err: errors.New(fmt.Sprintf("failed to forking invoke provider %v, but not legal resp", selected))} + return &protocol.RPCResult{Err: fmt.Errorf("failed to forking invoke provider %v, but not legal resp", selected)} } + return result } diff --git a/cluster/cluster_impl/forking_cluster_test.go b/cluster/cluster_impl/forking_cluster_test.go index 8603f8aedc4e28a3a4ca2f115355debc1a5ecc62..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 { @@ -87,7 +87,7 @@ func Test_ForkingInvokeSuccess(t *testing.T) { clusterInvoker := registerForking(t, invokers...) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) wg.Wait() } @@ -117,7 +117,7 @@ func Test_ForkingInvokeTimeout(t *testing.T) { clusterInvoker := registerForking(t, invokers...) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.NotNil(t, result.Error()) wg.Wait() @@ -156,7 +156,7 @@ func Test_ForkingInvokeHalfTimeout(t *testing.T) { clusterInvoker := registerForking(t, invokers...) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) wg.Wait() } diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go index 50b2735554b61600fb090f382f3d2920b3d445e3..943c2add68281d01e320252d07b7d58e27b51283 100644 --- a/cluster/cluster_impl/mock_cluster.go +++ b/cluster/cluster_impl/mock_cluster.go @@ -24,6 +24,7 @@ import ( type mockCluster struct{} +// NewMockCluster ... func NewMockCluster() cluster.Cluster { return &mockCluster{} } diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go index f4a28d6dcd5fbab8c62ee1f79bdd8576d8774a4c..079b688da65b3e6f6595212ad6e93c3b6ecc6504 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/registry_aware_cluster.go @@ -29,6 +29,7 @@ func init() { extension.SetCluster("registryAware", NewRegistryAwareCluster) } +// NewRegistryAwareCluster ... func NewRegistryAwareCluster() cluster.Cluster { return ®istryAwareCluster{} } diff --git a/cluster/cluster_impl/registry_aware_cluster_invoker.go b/cluster/cluster_impl/registry_aware_cluster_invoker.go index 5785c02489f95168d5419f0087f38b07c851a4a3..cded5bf16432e6b0c590e15b81c28369889a5f88 100644 --- a/cluster/cluster_impl/registry_aware_cluster_invoker.go +++ b/cluster/cluster_impl/registry_aware_cluster_invoker.go @@ -17,6 +17,9 @@ package cluster_impl +import ( + "context" +) import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" @@ -33,19 +36,19 @@ func newRegistryAwareClusterInvoker(directory cluster.Directory) protocol.Invoke } } -func (invoker *registryAwareClusterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (invoker *registryAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) //First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'default' key. for _, invoker := range invokers { if invoker.IsAvailable() && invoker.GetUrl().GetParam(constant.REGISTRY_DEFAULT_KEY, "false") == "true" { - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } } //If none of the invokers has a local signal, pick the first one available. for _, invoker := range invokers { if invoker.IsAvailable() { - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } } return nil diff --git a/cluster/cluster_impl/registry_aware_cluster_test.go b/cluster/cluster_impl/registry_aware_cluster_test.go index 4ae15cc5066c70646dee66cf4ef601202653cb07..3d0dcc0159839eb0a08aed842ee084449458c645 100644 --- a/cluster/cluster_impl/registry_aware_cluster_test.go +++ b/cluster/cluster_impl/registry_aware_cluster_test.go @@ -39,13 +39,13 @@ func Test_RegAwareInvokeSuccess(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, NewMockInvoker(url, 1)) } staticDir := directory.NewStaticDirectory(invokers) clusterInvoker := regAwareCluster.Join(staticDir) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) count = 0 } @@ -55,14 +55,14 @@ func TestDestroy(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, NewMockInvoker(url, 1)) } staticDir := directory.NewStaticDirectory(invokers) clusterInvoker := regAwareCluster.Join(staticDir) assert.Equal(t, true, clusterInvoker.IsAvailable()) - result := clusterInvoker.Invoke(&invocation.RPCInvocation{}) + result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NoError(t, result.Error()) count = 0 clusterInvoker.Destroy() diff --git a/cluster/directory.go b/cluster/directory.go index 045296ce549b13c327b6f479ca0bd75d0b6ce131..5a03b3a4490ce0b3aadece8a9ef43395f845dd12 100644 --- a/cluster/directory.go +++ b/cluster/directory.go @@ -22,7 +22,8 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Extension - Directory +// Directory +//Extension - Directory type Directory interface { common.Node List(invocation protocol.Invocation) []protocol.Invoker diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 1d59b51cc36858b80fb43c1d76e368e89e26ae36..75d9ef26567df0fbd83f5d9f94c8548d1e8e633d 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -20,32 +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" ) +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 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{}, } } + +// 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 Get URL instance func (dir *BaseDirectory) GetDirectoryUrl() *common.URL { return dir.url } +// 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() @@ -54,6 +116,18 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) { } } +// 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 e7a0e6e569db620ee83521505c9568199d45fe1e..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,6 +32,7 @@ type staticDirectory struct { invokers []protocol.Invoker } +// NewStaticDirectory Create a new staticDirectory with invokers func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory { var url common.URL @@ -52,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 { @@ -65,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.go b/cluster/loadbalance.go index 9ae4e4eb808b28581d12b72829c921c4f0cc9ac8..fb3641a77377eabbd692729a32e2c0c096282f18 100644 --- a/cluster/loadbalance.go +++ b/cluster/loadbalance.go @@ -21,7 +21,8 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Extension - LoadBalance +// LoadBalance +//Extension - LoadBalance type LoadBalance interface { Select([]protocol.Invoker, protocol.Invocation) protocol.Invoker } diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go new file mode 100644 index 0000000000000000000000000000000000000000..957c110663d6c56ada15543d372e210fa83bf74b --- /dev/null +++ b/cluster/loadbalance/consistent_hash.go @@ -0,0 +1,174 @@ +/* + * 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 loadbalance + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "hash/crc32" + "regexp" + "sort" + "strconv" + "strings" +) + +import ( + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" +) + +const ( + // ConsistentHash ... + ConsistentHash = "consistenthash" + // HashNodes ... + HashNodes = "hash.nodes" + // HashArguments ... + HashArguments = "hash.arguments" +) + +var ( + selectors = make(map[string]*ConsistentHashSelector) + re = regexp.MustCompile(constant.COMMA_SPLIT_PATTERN) +) + +func init() { + extension.SetLoadbalance(ConsistentHash, NewConsistentHashLoadBalance) +} + +// ConsistentHashLoadBalance ... +type ConsistentHashLoadBalance struct { +} + +// NewConsistentHashLoadBalance ... +func NewConsistentHashLoadBalance() cluster.LoadBalance { + return &ConsistentHashLoadBalance{} +} + +// Select ... +func (lb *ConsistentHashLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { + methodName := invocation.MethodName() + key := invokers[0].GetUrl().ServiceKey() + "." + methodName + + // hash the invokers + bs := make([]byte, 0) + for _, invoker := range invokers { + b, err := json.Marshal(invoker) + if err != nil { + return nil + } + bs = append(bs, b...) + } + hashCode := crc32.ChecksumIEEE(bs) + selector, ok := selectors[key] + if !ok || selector.hashCode != hashCode { + selectors[key] = newConsistentHashSelector(invokers, methodName, hashCode) + selector = selectors[key] + } + return selector.Select(invocation) +} + +// Uint32Slice ... +type Uint32Slice []uint32 + +func (s Uint32Slice) Len() int { + return len(s) +} + +func (s Uint32Slice) Less(i, j int) bool { + return s[i] < s[j] +} + +func (s Uint32Slice) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// ConsistentHashSelector ... +type ConsistentHashSelector struct { + hashCode uint32 + replicaNum int + virtualInvokers map[uint32]protocol.Invoker + keys Uint32Slice + argumentIndex []int +} + +func newConsistentHashSelector(invokers []protocol.Invoker, methodName string, + hashCode uint32) *ConsistentHashSelector { + + selector := &ConsistentHashSelector{} + selector.virtualInvokers = make(map[uint32]protocol.Invoker) + selector.hashCode = hashCode + url := invokers[0].GetUrl() + selector.replicaNum = int(url.GetMethodParamInt(methodName, HashNodes, 160)) + indices := re.Split(url.GetMethodParam(methodName, HashArguments, "0"), -1) + for _, index := range indices { + i, err := strconv.Atoi(index) + if err != nil { + return nil + } + selector.argumentIndex = append(selector.argumentIndex, i) + } + for _, invoker := range invokers { + u := invoker.GetUrl() + address := u.Ip + ":" + u.Port + for i := 0; i < selector.replicaNum/4; i++ { + digest := md5.Sum([]byte(address + strconv.Itoa(i))) + for j := 0; j < 4; j++ { + key := selector.hash(digest, j) + selector.keys = append(selector.keys, key) + selector.virtualInvokers[key] = invoker + } + } + } + sort.Sort(selector.keys) + return selector +} + +// Select ... +func (c *ConsistentHashSelector) Select(invocation protocol.Invocation) protocol.Invoker { + key := c.toKey(invocation.Arguments()) + digest := md5.Sum([]byte(key)) + return c.selectForKey(c.hash(digest, 0)) +} + +func (c *ConsistentHashSelector) toKey(args []interface{}) string { + var sb strings.Builder + for i := range c.argumentIndex { + if i >= 0 && i < len(args) { + fmt.Fprint(&sb, args[i].(string)) + } + } + return sb.String() +} + +func (c *ConsistentHashSelector) selectForKey(hash uint32) protocol.Invoker { + idx := sort.Search(len(c.keys), func(i int) bool { + return c.keys[i] >= hash + }) + if idx == len(c.keys) { + idx = 0 + } + return c.virtualInvokers[c.keys[idx]] +} + +func (c *ConsistentHashSelector) hash(digest [16]byte, i int) uint32 { + return uint32((digest[3+i*4]&0xFF)<<24) | uint32((digest[2+i*4]&0xFF)<<16) | + uint32((digest[1+i*4]&0xFF)<<8) | uint32(digest[i*4]&0xFF)&0xFFFFFFF +} diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a44293172c6e2c96bd098a19306f69260b713689 --- /dev/null +++ b/cluster/loadbalance/consistent_hash_test.go @@ -0,0 +1,109 @@ +/* + * 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 loadbalance + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/suite" +) + +import ( + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestConsistentHashSelectorSuite(t *testing.T) { + suite.Run(t, new(consistentHashSelectorSuite)) +} + +type consistentHashSelectorSuite struct { + suite.Suite + selector *ConsistentHashSelector +} + +func (s *consistentHashSelectorSuite) SetupTest() { + var invokers []protocol.Invoker + 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) +} + +func (s *consistentHashSelectorSuite) TestToKey() { + result := s.selector.toKey([]interface{}{"username", "age"}) + s.Equal(result, "usernameage") +} + +func (s *consistentHashSelectorSuite) TestSelectForKey() { + 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) + s.selector.keys = []uint32{99874, 9999945} + result := s.selector.selectForKey(9999944) + s.Equal(result.GetUrl().String(), "dubbo://192.168.1.0:8081?") +} + +func TestConsistentHashLoadBalanceSuite(t *testing.T) { + suite.Run(t, new(consistentHashLoadBalanceSuite)) +} + +type consistentHashLoadBalanceSuite struct { + suite.Suite + url1 common.URL + url2 common.URL + url3 common.URL + invokers []protocol.Invoker + invoker1 protocol.Invoker + invoker2 protocol.Invoker + invoker3 protocol.Invoker + lb cluster.LoadBalance +} + +func (s *consistentHashLoadBalanceSuite) SetupTest() { + var err error + 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("dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.NoError(err) + 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) + s.invoker2 = protocol.NewBaseInvoker(s.url2) + s.invoker3 = protocol.NewBaseInvoker(s.url3) + + s.invokers = append(s.invokers, s.invoker1, s.invoker2, s.invoker3) + s.lb = NewConsistentHashLoadBalance() +} + +func (s *consistentHashLoadBalanceSuite) TestSelect() { + args := []interface{}{"name", "password", "age"} + invoker := s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil)) + s.Equal(invoker.GetUrl().Location, "192.168.1.0:8080") + + args = []interface{}{"ok", "abc"} + invoker = s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil)) + s.Equal(invoker.GetUrl().Location, "192.168.1.0:8082") +} diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go index aa69f3cc207ae7465bc6d5472bc075d0902c8978..e7c41aac93e8d3dfcef5d49fa486483bd045f569 100644 --- a/cluster/loadbalance/least_active.go +++ b/cluster/loadbalance/least_active.go @@ -28,6 +28,7 @@ import ( ) const ( + // LeastActive ... LeastActive = "leastactive" ) @@ -38,6 +39,7 @@ func init() { type leastActiveLoadBalance struct { } +// NewLeastActiveLoadBalance ... func NewLeastActiveLoadBalance() cluster.LoadBalance { return &leastActiveLoadBalance{} } @@ -52,18 +54,18 @@ func (lb *leastActiveLoadBalance) Select(invokers []protocol.Invoker, invocation } var ( - leastActive int32 = -1 // The least active value of all invokers - totalWeight int64 = 0 // The number of invokers having the same least active value (LEAST_ACTIVE) - firstWeight int64 = 0 // Initial value, used for comparison - leastIndexes = make([]int, count) // The index of invokers having the same least active value (LEAST_ACTIVE) - leastCount = 0 // The number of invokers having the same least active value (LEAST_ACTIVE) - sameWeight = true // Every invoker has the same weight value? + leastActive int32 = -1 // The least active value of all invokers + totalWeight int64 // The number of invokers having the same least active value (LEAST_ACTIVE) + firstWeight int64 // Initial value, used for comparison + leastCount int // The number of invokers having the same least active value (LEAST_ACTIVE) + leastIndexes = make([]int, count) // The index of invokers having the same least active value (LEAST_ACTIVE) + sameWeight = true // Every invoker has the same weight value? ) for i := 0; i < count; i++ { invoker := invokers[i] // Active number - active := protocol.GetStatus(invoker.GetUrl(), invocation.MethodName()).GetActive() + active := protocol.GetMethodStatus(invoker.GetUrl(), invocation.MethodName()).GetActive() // current weight (maybe in warmUp) weight := GetWeight(invoker, invocation) // There are smaller active services 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.go b/cluster/loadbalance/random.go index 919792162dc527fa8c1e5cf2911f2933fa8232ef..56f13631b653ed070dae7def5bea97d924141209 100644 --- a/cluster/loadbalance/random.go +++ b/cluster/loadbalance/random.go @@ -38,6 +38,7 @@ func init() { type randomLoadBalance struct { } +// NewRandomLoadBalance ... func NewRandomLoadBalance() cluster.LoadBalance { return &randomLoadBalance{} } 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.go b/cluster/loadbalance/round_robin.go index 075acac7cdc60086ececb7b655dee86ec5198369..4d039999677aefb1093071666a845279dc357ce9 100644 --- a/cluster/loadbalance/round_robin.go +++ b/cluster/loadbalance/round_robin.go @@ -31,16 +31,19 @@ import ( ) const ( + // RoundRobin ... RoundRobin = "roundrobin" + // COMPLETE ... COMPLETE = 0 + // UPDATING ... UPDATING = 1 ) var ( - methodWeightMap sync.Map // [string]invokers - state int32 = COMPLETE // update lock acquired ? - recyclePeriod int64 = 60 * time.Second.Nanoseconds() + methodWeightMap sync.Map // [string]invokers + state = int32(COMPLETE) // update lock acquired ? + recyclePeriod = 60 * time.Second.Nanoseconds() ) func init() { @@ -49,6 +52,7 @@ func init() { type roundRobinLoadBalance struct{} +// NewRoundRobinLoadBalance ... func NewRoundRobinLoadBalance() cluster.LoadBalance { return &roundRobinLoadBalance{} } 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/loadbalance/util.go b/cluster/loadbalance/util.go index 7e0c2e265073c0a96032a6dd3294a6d73c1a4001..9f36ad9379a3a09a4a058f6179e3e537b9e105bc 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -26,6 +26,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// GetWeight ... func GetWeight(invoker protocol.Invoker, invocation protocol.Invocation) int64 { url := invoker.GetUrl() weight := url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) 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 54% rename from cluster/router/condition_router_test.go rename to cluster/router/condition/factory_test.go index 7d8b0d88cab688e6ea10d1562a27de4609d51f58..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 } @@ -93,15 +93,19 @@ type rest struct { var count int -func (bi *MockInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { count++ - var success bool - var err error = nil + + var ( + success bool + err error + ) if count >= bi.successCount { success = true } else { err = perrors.New("error") } + result := &protocol.RPCResult{Err: err, Rest: rest{tried: count, success: success}} return result } @@ -115,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")) @@ -158,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)) @@ -183,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]) @@ -259,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]) @@ -278,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]) @@ -297,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]) @@ -316,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 62% rename from cluster/router/condition_router.go rename to cluster/router/condition/router.go index a196ceb5771422f06a820986a02499f9fe3523dc..c5d46444bde921386d14a8be7eb0a89d855f8ece 100644 --- a/cluster/router/condition_router.go +++ b/cluster/router/condition/router.go @@ -15,54 +15,55 @@ * limitations under the License. */ -package router +package condition import ( - "reflect" "regexp" "strings" ) import ( - "github.com/dubbogo/gost/container/gxset" - 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 = `([&!=,]*)\\s*([^&!=,\\s]+)` - FORCE = "force" - 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, "=>") @@ -95,30 +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 } -//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 } @@ -126,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) } @@ -145,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 } @@ -156,14 +181,12 @@ func parseRule(rule string) (map[string]MatchPair, error) { if len(rule) == 0 { return condition, nil } - var pair MatchPair + + var ( + pair MatchPair + ) values := gxset.NewSet() - reg := regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`) - var startIndex = 0 - 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]) @@ -186,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) } @@ -209,25 +236,33 @@ func parseRule(rule string) (map[string]MatchPair, error) { return condition, nil } -// -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 +} + +// 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 } -//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 +// 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, error) { +// 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) } - result := false + var result bool for key, matchPair := range pairs { var sampleValue string @@ -241,21 +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 - } else { - result = true + return false } + + result = true } else { if !(matchPair.Matches.Empty()) { - return false, nil - } else { - result = true + return false } + + result = true } } - return result, nil + return result } +// MatchPair Match key pair , condition process type MatchPair struct { Matches *gxset.HashSet Mismatches *gxset.HashSet @@ -265,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 } } @@ -274,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 } } @@ -295,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/router_factory.go b/cluster/router/health_checker.go similarity index 64% rename from cluster/router/router_factory.go rename to cluster/router/health_checker.go index a9794cb885badae98445ef4d7c0bbc2230d25d5f..d9e3087a272dd500cdd1dc9dc6680d436891f88b 100644 --- a/cluster/router/router_factory.go +++ b/cluster/router/health_checker.go @@ -18,20 +18,11 @@ package router import ( - "github.com/apache/dubbo-go/cluster" - "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) -} - -type ConditionRouterFactory struct{} - -func NewConditionRouterFactory() cluster.RouterFactory { - return ConditionRouterFactory{} -} -func (c ConditionRouterFactory) Router(url *common.URL) (cluster.Router, error) { - return newConditionRouter(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/config/environment.go b/common/config/environment.go index 931f0460917d68d3bd7adcd6a6bc1b49c7d3bba8..071af31152ba4ce3c579f70aa23df59d718ce506 100644 --- a/common/config/environment.go +++ b/common/config/environment.go @@ -27,6 +27,7 @@ import ( "github.com/apache/dubbo-go/config_center" ) +// Environment // There is dubbo.properties file and application level config center configuration which higner than normal config center in java. So in java the // configuration sequence will be config center > application level config center > dubbo.properties > spring bean configuration. // But in go, neither the dubbo.properties file or application level config center configuration will not support for the time being. @@ -45,12 +46,15 @@ var ( once sync.Once ) +// GetEnvInstance ... func GetEnvInstance() *Environment { once.Do(func() { instance = &Environment{configCenterFirst: true} }) return instance } + +// NewEnvInstance ... func NewEnvInstance() { instance = &Environment{configCenterFirst: true} } @@ -63,34 +67,40 @@ func NewEnvInstance() { // return env.configCenterFirst //} +// UpdateExternalConfigMap ... func (env *Environment) UpdateExternalConfigMap(externalMap map[string]string) { for k, v := range externalMap { env.externalConfigMap.Store(k, v) } } +// UpdateAppExternalConfigMap ... func (env *Environment) UpdateAppExternalConfigMap(externalMap map[string]string) { for k, v := range externalMap { env.appExternalConfigMap.Store(k, v) } } +// Configuration ... func (env *Environment) Configuration() *list.List { - list := list.New() + cfgList := list.New() // The sequence would be: SystemConfiguration -> ExternalConfiguration -> AppExternalConfiguration -> AbstractConfig -> PropertiesConfiguration - list.PushFront(newInmemoryConfiguration(&(env.externalConfigMap))) - list.PushFront(newInmemoryConfiguration(&(env.appExternalConfigMap))) - return list + cfgList.PushFront(newInmemoryConfiguration(&(env.externalConfigMap))) + cfgList.PushFront(newInmemoryConfiguration(&(env.appExternalConfigMap))) + return cfgList } +// SetDynamicConfiguration ... func (env *Environment) SetDynamicConfiguration(dc config_center.DynamicConfiguration) { env.dynamicConfiguration = dc } +// GetDynamicConfiguration ... func (env *Environment) GetDynamicConfiguration() config_center.DynamicConfiguration { return env.dynamicConfiguration } +// InmemoryConfiguration ... type InmemoryConfiguration struct { store *sync.Map } @@ -99,6 +109,7 @@ func newInmemoryConfiguration(p *sync.Map) *InmemoryConfiguration { return &InmemoryConfiguration{store: p} } +// GetProperty ... func (conf *InmemoryConfiguration) GetProperty(key string) (bool, string) { if conf.store == nil { return false, "" @@ -112,6 +123,7 @@ func (conf *InmemoryConfiguration) GetProperty(key string) (bool, string) { return false, "" } +// GetSubProperty ... func (conf *InmemoryConfiguration) GetSubProperty(subKey string) map[string]struct{} { if conf.store == nil { return nil diff --git a/common/constant/default.go b/common/constant/default.go index cb6d68af0561d44f4306f16973a89759c9a9ac37..8ed645e84a724531080eff6efe5fdb0df5479e80 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -46,8 +46,8 @@ const ( const ( DEFAULT_KEY = "default" PREFIX_DEFAULT_KEY = "default." - DEFAULT_SERVICE_FILTERS = "echo,token,accesslog,tps,execute" - DEFAULT_REFERENCE_FILTERS = "" + DEFAULT_SERVICE_FILTERS = "echo,token,accesslog,tps,generic_service,execute,pshutdown" + DEFAULT_REFERENCE_FILTERS = "cshutdown" GENERIC_REFERENCE_FILTERS = "generic" GENERIC = "$invoke" ECHO = "$echo" @@ -66,4 +66,9 @@ const ( DYNAMIC_CONFIGURATORS_CATEGORY = "dynamicconfigurators" APP_DYNAMIC_CONFIGURATORS_CATEGORY = "appdynamicconfigurators" PROVIDER_CATEGORY = "providers" + CONSUMER_CATEGORY = "consumers" +) + +const ( + COMMA_SPLIT_PATTERN = "\\s*[,]+\\s*" ) diff --git a/common/constant/env.go b/common/constant/env.go index 759cb0be0a2bd36a2a345a360c541b7d56813d70..5376323328f431083a47395c9e2ebbab5b37f307 100644 --- a/common/constant/env.go +++ b/common/constant/env.go @@ -18,7 +18,12 @@ package constant const ( + // CONF_CONSUMER_FILE_PATH ... CONF_CONSUMER_FILE_PATH = "CONF_CONSUMER_FILE_PATH" + // CONF_PROVIDER_FILE_PATH ... CONF_PROVIDER_FILE_PATH = "CONF_PROVIDER_FILE_PATH" - APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" + // 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 ff371d08c669df07f24a3e81e0be0a29a6b17ddd..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 ( @@ -55,6 +56,7 @@ const ( WEIGHT_KEY = "weight" WARMUP_KEY = "warmup" RETRIES_KEY = "retries" + STICKY_KEY = "sticky" BEAN_NAME = "bean.name" FAIL_BACK_TASKS_KEY = "failbacktasks" FORKS_KEY = "forks" @@ -71,6 +73,8 @@ const ( EXECUTE_LIMIT_KEY = "execute.limit" DEFAULT_EXECUTE_LIMIT = "-1" EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler" + PROVIDER_SHUTDOWN_FILTER = "pshutdown" + CONSUMER_SHUTDOWN_FILTER = "cshutdown" ) const ( @@ -86,20 +90,31 @@ 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 ( CONFIG_NAMESPACE_KEY = "config.namespace" + CONFIG_GROUP_KEY = "config.group" + CONFIG_APP_ID_KEY = "config.appId" + CONFIG_CLUSTER_KEY = "config.cluster" + CONFIG_CHECK_KEY = "config.check" CONFIG_TIMEOUT_KET = "config.timeout" CONFIG_VERSION_KEY = "configVersion" COMPATIBLE_CONFIG_KEY = "compatible_config" @@ -112,6 +127,8 @@ const ( ProtocolConfigPrefix = "dubbo.protocols." ProviderConfigPrefix = "dubbo.provider." ConsumerConfigPrefix = "dubbo.consumer." + ShutdownConfigPrefix = "dubbo.shutdown." + RouterConfigPrefix = "dubbo.router." ) const ( @@ -129,3 +146,69 @@ const ( NACOS_PROTOCOL_KEY = "protocol" NACOS_PATH_KEY = "path" ) + +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" + SERVICE_AUTH_KEY = "auth" + AUTHENTICATOR_KEY = "authenticator" + DEFAULT_AUTHENTICATOR = "accesskeys" + DEFAULT_ACCESS_KEY_STORAGE = "urlstorage" + ACCESS_KEY_STORAGE_KEY = "accessKey.storage" + REQUEST_TIMESTAMP_KEY = "timestamp" + REQUEST_SIGNATURE_KEY = "signature" + AK_KEY = "ak" + SIGNATURE_STRING_FORMAT = "%s#%s#%s#%s" + PARAMTER_SIGNATURE_ENABLE_KEY = "param.sign" + CONSUMER = "consumer" + 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/constant/time.go b/common/constant/time.go new file mode 100644 index 0000000000000000000000000000000000000000..be1baaca67f474aa92e86e529d03400948ef4612 --- /dev/null +++ b/common/constant/time.go @@ -0,0 +1,26 @@ +/* + * 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 constant + +import ( + "time" +) + +var ( + MsToNanoRate = int64(time.Millisecond / time.Nanosecond) +) diff --git a/common/constant/version.go b/common/constant/version.go index d4c6821e76894cbd82dc5fae09124263b5c6aa0f..730224376054a36b0c7cfeda7d5ea5e7ce058618 100644 --- a/common/constant/version.go +++ b/common/constant/version.go @@ -18,7 +18,10 @@ package constant const ( - Version = "2.6.0" - Name = "dubbogo" - DATE = "2019/05/06" + // Version apache/dubbo-go version + Version = "1.3.0" + // Name module name + Name = "dubbogo" + // Date release date + DATE = "2020/01/12" ) diff --git a/common/extension/auth.go b/common/extension/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..e57e22f660b6d4dec63f8b4a06c25b05bd5c8d72 --- /dev/null +++ b/common/extension/auth.go @@ -0,0 +1,32 @@ +package extension + +import ( + "github.com/apache/dubbo-go/filter" +) + +var ( + authenticators = make(map[string]func() filter.Authenticator) + accesskeyStorages = make(map[string]func() filter.AccessKeyStorage) +) + +func SetAuthenticator(name string, fcn func() filter.Authenticator) { + authenticators[name] = fcn +} + +func GetAuthenticator(name string) filter.Authenticator { + if authenticators[name] == nil { + panic("authenticator for " + name + " is not existing, make sure you have import the package.") + } + return authenticators[name]() +} + +func SetAccesskeyStorages(name string, fcn func() filter.AccessKeyStorage) { + accesskeyStorages[name] = fcn +} + +func GetAccesskeyStorages(name string) filter.AccessKeyStorage { + if accesskeyStorages[name] == nil { + panic("accesskeyStorages for " + name + " is not existing, make sure you have import the package.") + } + return accesskeyStorages[name]() +} diff --git a/common/extension/cluster.go b/common/extension/cluster.go index 91e9f953b505e31c1a4f448e1504a6ae50a9663f..b2d81f6b1e56bb487b1d408b878308f6dfe042e4 100644 --- a/common/extension/cluster.go +++ b/common/extension/cluster.go @@ -25,10 +25,12 @@ var ( clusters = make(map[string]func() cluster.Cluster) ) +// SetCluster ... func SetCluster(name string, fcn func() cluster.Cluster) { clusters[name] = fcn } +// GetCluster ... func GetCluster(name string) cluster.Cluster { if clusters[name] == nil { panic("cluster for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_center.go b/common/extension/config_center.go index be4b62ccdd9c36500c306c7f16abd054f91ae86b..03d27db46c94b0ea0e212646077d97f948a8e328 100644 --- a/common/extension/config_center.go +++ b/common/extension/config_center.go @@ -26,10 +26,12 @@ var ( configCenters = make(map[string]func(config *common.URL) (config_center.DynamicConfiguration, error)) ) +// SetConfigCenter ... func SetConfigCenter(name string, v func(config *common.URL) (config_center.DynamicConfiguration, error)) { configCenters[name] = v } +// GetConfigCenter ... func GetConfigCenter(name string, config *common.URL) (config_center.DynamicConfiguration, error) { if configCenters[name] == nil { panic("config center for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_center_factory.go b/common/extension/config_center_factory.go index 82e0ef6ebcf632ccff32aec5c69c2082a28c51af..85913fdce1ed3472c2bd9eb4aadbb0f631481dbd 100644 --- a/common/extension/config_center_factory.go +++ b/common/extension/config_center_factory.go @@ -25,10 +25,12 @@ var ( configCenterFactories = make(map[string]func() config_center.DynamicConfigurationFactory) ) +// SetConfigCenterFactory ... func SetConfigCenterFactory(name string, v func() config_center.DynamicConfigurationFactory) { configCenterFactories[name] = v } +// GetConfigCenterFactory ... func GetConfigCenterFactory(name string) config_center.DynamicConfigurationFactory { if configCenterFactories[name] == nil { panic("config center for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/configurator.go b/common/extension/configurator.go index 40d134f474ae792afb76f1d8e3f56d172bfd07e2..de98f8a260ea1f3a2e2a1f32c82dc869585e2789 100644 --- a/common/extension/configurator.go +++ b/common/extension/configurator.go @@ -22,7 +22,10 @@ import ( "github.com/apache/dubbo-go/config_center" ) -const DefaultKey = "default" +const ( + // DefaultKey ... + DefaultKey = "default" +) type getConfiguratorFunc func(url *common.URL) config_center.Configurator @@ -30,10 +33,12 @@ var ( configurator = make(map[string]getConfiguratorFunc) ) +// SetConfigurator ... func SetConfigurator(name string, v getConfiguratorFunc) { configurator[name] = v } +// GetConfigurator ... func GetConfigurator(name string, url *common.URL) config_center.Configurator { if configurator[name] == nil { panic("configurator for " + name + " is not existing, make sure you have import the package.") @@ -41,10 +46,13 @@ func GetConfigurator(name string, url *common.URL) config_center.Configurator { return configurator[name](url) } + +// SetDefaultConfigurator ... func SetDefaultConfigurator(v getConfiguratorFunc) { configurator[DefaultKey] = v } +// GetDefaultConfigurator ... func GetDefaultConfigurator(url *common.URL) config_center.Configurator { if configurator[DefaultKey] == nil { panic("configurator for default is not existing, make sure you have import the package.") @@ -52,6 +60,8 @@ func GetDefaultConfigurator(url *common.URL) config_center.Configurator { return configurator[DefaultKey](url) } + +// GetDefaultConfiguratorFunc ... func GetDefaultConfiguratorFunc() getConfiguratorFunc { if configurator[DefaultKey] == nil { panic("configurator for default is not existing, make sure you have import the package.") diff --git a/common/extension/filter.go b/common/extension/filter.go index e2a66c7449448a2229c53eabb478f2c96a429bc7..deea2d908bc2741e0f15ecc36e9d4fc5975e531e 100644 --- a/common/extension/filter.go +++ b/common/extension/filter.go @@ -19,30 +19,33 @@ package extension import ( "github.com/apache/dubbo-go/filter" - "github.com/apache/dubbo-go/filter/common" ) var ( filters = make(map[string]func() filter.Filter) - rejectedExecutionHandler = make(map[string]func() common.RejectedExecutionHandler) + rejectedExecutionHandler = make(map[string]func() filter.RejectedExecutionHandler) ) +// SetFilter ... func SetFilter(name string, v func() filter.Filter) { filters[name] = v } +// GetFilter ... func GetFilter(name string) filter.Filter { if filters[name] == nil { - panic("filter for " + name + " is not existing, make sure you have import the package.") + panic("filter for " + name + " is not existing, make sure you have imported the package.") } return filters[name]() } -func SetRejectedExecutionHandler(name string, creator func() common.RejectedExecutionHandler) { +// SetRejectedExecutionHandler ... +func SetRejectedExecutionHandler(name string, creator func() filter.RejectedExecutionHandler) { rejectedExecutionHandler[name] = creator } -func GetRejectedExecutionHandler(name string) common.RejectedExecutionHandler { +// GetRejectedExecutionHandler ... +func GetRejectedExecutionHandler(name string) filter.RejectedExecutionHandler { creator, ok := rejectedExecutionHandler[name] if !ok { panic("RejectedExecutionHandler for " + name + " is not existing, make sure you have import the package " + diff --git a/common/extension/graceful_shutdown.go b/common/extension/graceful_shutdown.go new file mode 100644 index 0000000000000000000000000000000000000000..3abd75c0aa328f3553c3d83340ae440b8dfe3356 --- /dev/null +++ b/common/extension/graceful_shutdown.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 extension + +import ( + "container/list" +) + +var ( + customShutdownCallbacks = list.New() +) + +/** + * AddCustomShutdownCallback + * you should not make any assumption about the order. + * For example, if you have more than one callbacks, and you wish the order is: + * callback1() + * callback2() + * ... + * callbackN() + * Then you should put then together: + * func callback() { + * callback1() + * callback2() + * ... + * callbackN() + * } + * I think the order of custom callbacks should be decided by the users. + * Even though I can design a mechanism to support the ordered custom callbacks, + * the benefit of that mechanism is low. + * And it may introduce much complication for another users. + */ +func AddCustomShutdownCallback(callback func()) { + customShutdownCallbacks.PushBack(callback) +} + +// GetAllCustomShutdownCallbacks ... +func GetAllCustomShutdownCallbacks() *list.List { + return customShutdownCallbacks +} 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/common/extension/health_checker_test.go b/common/extension/health_checker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ec934e6e9cedc5acbef350f17b87b0b2e37bc844 --- /dev/null +++ b/common/extension/health_checker_test.go @@ -0,0 +1,49 @@ +/* + * 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 ( + "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/protocol" +) + +func TestGetHealthChecker(t *testing.T) { + SethealthChecker("mock", newMockhealthCheck) + checker := GetHealthChecker("mock", common.NewURLWithOptions()) + assert.NotNil(t, checker) +} + +type mockHealthChecker struct { +} + +func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool { + return true +} + +func newMockhealthCheck(url *common.URL) router.HealthChecker { + return &mockHealthChecker{} +} diff --git a/common/extension/loadbalance.go b/common/extension/loadbalance.go index f1f97b9399a2b33a3e06213fc0b2f84e73b002b7..0d557a4640ed892a18ad59a3247763ab5807a593 100644 --- a/common/extension/loadbalance.go +++ b/common/extension/loadbalance.go @@ -25,10 +25,12 @@ var ( loadbalances = make(map[string]func() cluster.LoadBalance) ) +// SetLoadbalance ... func SetLoadbalance(name string, fcn func() cluster.LoadBalance) { loadbalances[name] = fcn } +// GetLoadbalance ... func GetLoadbalance(name string) cluster.LoadBalance { if loadbalances[name] == nil { panic("loadbalance for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/metrics.go b/common/extension/metrics.go new file mode 100644 index 0000000000000000000000000000000000000000..42fca7a2db36614fcef31dd5ba7324a156164d4f --- /dev/null +++ b/common/extension/metrics.go @@ -0,0 +1,44 @@ +/* + * 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/metrics" +) + +var ( + // we couldn't store the instance because the some instance may initialize before loading configuration + // so lazy initialization will be better. + metricReporterMap = make(map[string]func() metrics.Reporter, 4) +) + +// SetMetricReporter set a reporter with the name +func SetMetricReporter(name string, reporterFunc func() metrics.Reporter) { + metricReporterMap[name] = reporterFunc +} + +// GetMetricReporter find the reporter with name. +// if not found, it will panic. +// we should know that this method usually is called when system starts, so we should panic +func GetMetricReporter(name string) metrics.Reporter { + reporterFunc, found := metricReporterMap[name] + if !found { + panic("Cannot find the reporter with name: " + name) + } + return reporterFunc() +} diff --git a/common/extension/metrics_test.go b/common/extension/metrics_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6a8a3fe538a9cd68c57c91592a88ec257ae4a267 --- /dev/null +++ b/common/extension/metrics_test.go @@ -0,0 +1,49 @@ +/* + * 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 ( + "context" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +func TestGetMetricReporter(t *testing.T) { + reporter := &mockReporter{} + name := "mock" + SetMetricReporter(name, func() metrics.Reporter { + return reporter + }) + res := GetMetricReporter(name) + assert.Equal(t, reporter, res) +} + +type mockReporter struct { +} + +func (m mockReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { +} diff --git a/common/extension/protocol.go b/common/extension/protocol.go index 50d339476d024c04b182c38632689a99bc5c1680..009687a17ace8cea567248af655e04604d09d9b8 100644 --- a/common/extension/protocol.go +++ b/common/extension/protocol.go @@ -25,10 +25,12 @@ var ( protocols = make(map[string]func() protocol.Protocol) ) +// SetProtocol ... func SetProtocol(name string, v func() protocol.Protocol) { protocols[name] = v } +// GetProtocol ... func GetProtocol(name string) protocol.Protocol { if protocols[name] == nil { panic("protocol for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/proxy_factory.go b/common/extension/proxy_factory.go index 53cbbee54054bf8ad87964393b01ca6601106066..19826bb0560ea0d3fa471c04873b20a6878f57d8 100644 --- a/common/extension/proxy_factory.go +++ b/common/extension/proxy_factory.go @@ -22,18 +22,21 @@ import ( ) var ( - proxy_factories = make(map[string]func(...proxy.Option) proxy.ProxyFactory) + proxyFactories = make(map[string]func(...proxy.Option) proxy.ProxyFactory) ) +// SetProxyFactory ... func SetProxyFactory(name string, f func(...proxy.Option) proxy.ProxyFactory) { - proxy_factories[name] = f + proxyFactories[name] = f } + +// GetProxyFactory ... func GetProxyFactory(name string) proxy.ProxyFactory { if name == "" { name = "default" } - if proxy_factories[name] == nil { + if proxyFactories[name] == nil { panic("proxy factory for " + name + " is not existing, make sure you have import the package.") } - return proxy_factories[name]() + return proxyFactories[name]() } diff --git a/common/extension/registry.go b/common/extension/registry.go index 776c2b5df542e56f8c120c850f20093a971d8602..6ba746dc47382927d12ce39b7936212c5d75153d 100644 --- a/common/extension/registry.go +++ b/common/extension/registry.go @@ -26,10 +26,12 @@ var ( registrys = make(map[string]func(config *common.URL) (registry.Registry, error)) ) +// SetRegistry ... func SetRegistry(name string, v func(config *common.URL) (registry.Registry, error)) { registrys[name] = v } +// GetRegistry ... func GetRegistry(name string, config *common.URL) (registry.Registry, error) { if registrys[name] == nil { panic("registry for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go index 6f27aafaebf87147116e74272cc229657f436201..70d71dfa859b996030c865775a588da20039f9a5 100644 --- a/common/extension/router_factory.go +++ b/common/extension/router_factory.go @@ -18,21 +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) ) -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 } -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/extension/tps_limit.go b/common/extension/tps_limit.go index 65891c79336224f59b66f8312693c6b5151a7e28..c72c2b030fc0f391362189bfe18a65582543693a 100644 --- a/common/extension/tps_limit.go +++ b/common/extension/tps_limit.go @@ -18,19 +18,21 @@ package extension import ( - "github.com/apache/dubbo-go/filter/impl/tps" + "github.com/apache/dubbo-go/filter" ) var ( - tpsLimitStrategy = make(map[string]func(rate int, interval int) tps.TpsLimitStrategy) - tpsLimiter = make(map[string]func() tps.TpsLimiter) + tpsLimitStrategy = make(map[string]filter.TpsLimitStrategyCreator) + tpsLimiter = make(map[string]func() filter.TpsLimiter) ) -func SetTpsLimiter(name string, creator func() tps.TpsLimiter) { +// SetTpsLimiter ... +func SetTpsLimiter(name string, creator func() filter.TpsLimiter) { tpsLimiter[name] = creator } -func GetTpsLimiter(name string) tps.TpsLimiter { +// GetTpsLimiter ... +func GetTpsLimiter(name string) filter.TpsLimiter { creator, ok := tpsLimiter[name] if !ok { panic("TpsLimiter for " + name + " is not existing, make sure you have import the package " + @@ -39,11 +41,13 @@ func GetTpsLimiter(name string) tps.TpsLimiter { return creator() } -func SetTpsLimitStrategy(name string, creator func(rate int, interval int) tps.TpsLimitStrategy) { +// SetTpsLimitStrategy ... +func SetTpsLimitStrategy(name string, creator filter.TpsLimitStrategyCreator) { tpsLimitStrategy[name] = creator } -func GetTpsLimitStrategyCreator(name string) func(rate int, interval int) tps.TpsLimitStrategy { +// GetTpsLimitStrategyCreator ... +func GetTpsLimitStrategyCreator(name string) filter.TpsLimitStrategyCreator { creator, ok := tpsLimitStrategy[name] if !ok { panic("TpsLimitStrategy for " + name + " is not existing, make sure you have import the package " + diff --git a/common/logger/logger.go b/common/logger/logger.go index f41e95744f954da69b0e3695c97ba3389c69160a..016afe69808f2007541c617f406db64beb511f1c 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -40,6 +40,13 @@ var ( logger Logger ) +// DubboLogger ... +type DubboLogger struct { + Logger + dynamicLevel zap.AtomicLevel +} + +// Logger ... type Logger interface { Info(args ...interface{}) Warn(args ...interface{}) @@ -60,6 +67,7 @@ func init() { } } +// InitLog ... func InitLog(logConfFile string) error { if logConfFile == "" { InitLogger(nil) @@ -88,6 +96,7 @@ func InitLog(logConfFile string) error { return nil } +// InitLogger ... func InitLogger(conf *zap.Config) { var zapLoggerConfig zap.Config if conf == nil { @@ -109,17 +118,42 @@ func InitLogger(conf *zap.Config) { zapLoggerConfig = *conf } zapLogger, _ := zapLoggerConfig.Build(zap.AddCallerSkip(1)) - logger = zapLogger.Sugar() + //logger = zapLogger.Sugar() + logger = &DubboLogger{Logger: zapLogger.Sugar(), dynamicLevel: zapLoggerConfig.Level} // set getty log getty.SetLogger(logger) } +// SetLogger ... func SetLogger(log Logger) { logger = log getty.SetLogger(logger) } +// GetLogger ... func GetLogger() Logger { return logger } + +// SetLoggerLevel ... +func SetLoggerLevel(level string) bool { + if l, ok := logger.(OpsLogger); ok { + l.SetLoggerLevel(level) + return true + } + return false +} + +// OpsLogger ... +type OpsLogger interface { + Logger + SetLoggerLevel(level string) +} + +// SetLoggerLevel ... +func (dl *DubboLogger) SetLoggerLevel(level string) { + l := new(zapcore.Level) + l.Set(level) + dl.dynamicLevel.SetLevel(*l) +} diff --git a/common/logger/logger_test.go b/common/logger/logger_test.go index e29b7cbc8e9bbd67df41df5ac687a079621c3360..6081f71aecccbfab5fd574335effe7788b6ce799 100644 --- a/common/logger/logger_test.go +++ b/common/logger/logger_test.go @@ -65,3 +65,19 @@ func TestInitLog(t *testing.T) { Warnf("%s", "warn") Errorf("%s", "error") } + +func TestSetLevel(t *testing.T) { + err := InitLog("./log.yml") + assert.NoError(t, err) + Debug("debug") + Info("info") + + assert.True(t, SetLoggerLevel("info")) + Debug("debug") + Info("info") + + SetLogger(GetLogger().(*DubboLogger).Logger) + assert.False(t, SetLoggerLevel("debug")) + Debug("debug") + Info("info") +} diff --git a/common/logger/logging.go b/common/logger/logging.go index 4638c9a41dfe986d256e1ff4d52b690c1747fc94..36d48ee61e8a4a986abfbaa79f3d361cd81494f4 100644 --- a/common/logger/logging.go +++ b/common/logger/logging.go @@ -17,27 +17,42 @@ package logger +// Info ... func Info(args ...interface{}) { logger.Info(args...) } + +// Warn ... func Warn(args ...interface{}) { logger.Warn(args...) } + +// Error ... func Error(args ...interface{}) { logger.Error(args...) } + +// Debug ... func Debug(args ...interface{}) { logger.Debug(args...) } + +// Infof ... func Infof(fmt string, args ...interface{}) { logger.Infof(fmt, args...) } + +// Warnf ... func Warnf(fmt string, args ...interface{}) { logger.Warnf(fmt, args...) } + +// Errorf ... func Errorf(fmt string, args ...interface{}) { logger.Errorf(fmt, args...) } + +// Debugf ... func Debugf(fmt string, args ...interface{}) { logger.Debugf(fmt, args...) } diff --git a/common/node.go b/common/node.go index 992ead38d8acf85bbb14f02eebca4c4fe5a0a1e2..979eee31ef3a63eb21af6c9045aee7f6d784f2ba 100644 --- a/common/node.go +++ b/common/node.go @@ -17,6 +17,7 @@ package common +// Node ... type Node interface { GetUrl() URL IsAvailable() bool diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 1c079f6bca52bf8f6e8c5ebb168da82ab8ccb5f2..6765a810a5ed48d95f49b5b97fbf660dd8587715 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -18,6 +18,7 @@ package proxy import ( + "context" "reflect" "sync" ) @@ -39,8 +40,11 @@ type Proxy struct { once sync.Once } -var typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type() +var ( + typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type() +) +// NewProxy ... func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[string]string) *Proxy { return &Proxy{ invoke: invoke, @@ -49,11 +53,13 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str } } +// Implement // proxy implement // In consumer, RPCService like: // 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. @@ -72,10 +78,11 @@ func (p *Proxy) Implement(v common.RPCService) { makeDubboCallProxy := func(methodName string, outs []reflect.Type) func(in []reflect.Value) []reflect.Value { return func(in []reflect.Value) []reflect.Value { var ( - err error - inv *invocation_impl.RPCInvocation - inArr []interface{} - reply reflect.Value + err error + inv *invocation_impl.RPCInvocation + inIArr []interface{} + inVArr []reflect.Value + reply reflect.Value ) if methodName == "Echo" { methodName = "$echo" @@ -93,8 +100,13 @@ func (p *Proxy) Implement(v common.RPCService) { start := 0 end := len(in) + invCtx := context.Background() if end > 0 { if in[0].Type().String() == "context.Context" { + if !in[0].IsNil() { + // the user declared context as method's parameter + invCtx = in[0].Interface().(context.Context) + } start += 1 } if len(outs) == 1 && in[end-1].Type().Kind() == reflect.Ptr { @@ -104,30 +116,34 @@ func (p *Proxy) Implement(v common.RPCService) { } if end-start <= 0 { - inArr = []interface{}{} + inIArr = []interface{}{} + inVArr = []reflect.Value{} } else if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 { - inArr = v + inIArr = v + inVArr = []reflect.Value{in[start]} } else { - inArr = make([]interface{}, end-start) + inIArr = make([]interface{}, end-start) + inVArr = make([]reflect.Value, end-start) index := 0 for i := start; i < end; i++ { - inArr[index] = in[i].Interface() + inIArr[index] = in[i].Interface() + inVArr[index] = in[i] index++ } } inv = invocation_impl.NewRPCInvocationWithOptions(invocation_impl.WithMethodName(methodName), - invocation_impl.WithArguments(inArr), invocation_impl.WithReply(reply.Interface()), - invocation_impl.WithCallBack(p.callBack)) + invocation_impl.WithArguments(inIArr), invocation_impl.WithReply(reply.Interface()), + invocation_impl.WithCallBack(p.callBack), invocation_impl.WithParameterValues(inVArr)) for k, value := range p.attachments { inv.SetAttachments(k, value) } - result := p.invoke.Invoke(inv) + 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()} } @@ -178,6 +194,12 @@ func (p *Proxy) Implement(v common.RPCService) { } +// Get ... func (p *Proxy) Get() common.RPCService { return p.rpc } + +// GetCallback ... +func (p *Proxy) GetCallback() interface{} { + return p.callBack +} diff --git a/common/proxy/proxy_factory.go b/common/proxy/proxy_factory.go index 2567e0ee09cf7fa5aef7fde46872eb88205d8e45..7b249a3e9754b097130a80bf3819d282dad6b6e8 100644 --- a/common/proxy/proxy_factory.go +++ b/common/proxy/proxy_factory.go @@ -22,9 +22,12 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// ProxyFactory ... type ProxyFactory interface { GetProxy(invoker protocol.Invoker, url *common.URL) *Proxy + GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *Proxy GetInvoker(url common.URL) protocol.Invoker } +// Option ... type Option func(ProxyFactory) diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go index bafba60b400ec59d99e2d68ecf4d067c906ba6fb..114cfee2363022da5f7957a825a16fc42b8c928f 100644 --- a/common/proxy/proxy_factory/default.go +++ b/common/proxy/proxy_factory/default.go @@ -18,6 +18,7 @@ package proxy_factory import ( + "context" "reflect" "strings" ) @@ -39,6 +40,7 @@ func init() { extension.SetProxyFactory("default", NewDefaultProxyFactory) } +// DefaultProxyFactory ... type DefaultProxyFactory struct { //delegate ProxyFactory } @@ -51,26 +53,38 @@ type DefaultProxyFactory struct { // } //} +// NewDefaultProxyFactory ... func NewDefaultProxyFactory(options ...proxy.Option) proxy.ProxyFactory { return &DefaultProxyFactory{} } + +// GetProxy ... func (factory *DefaultProxyFactory) GetProxy(invoker protocol.Invoker, url *common.URL) *proxy.Proxy { + return factory.GetAsyncProxy(invoker, nil, url) +} + +// GetAsyncProxy ... +func (factory *DefaultProxyFactory) GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *proxy.Proxy { //create proxy attachments := map[string]string{} attachments[constant.ASYNC_KEY] = url.GetParam(constant.ASYNC_KEY, "false") - return proxy.NewProxy(invoker, nil, attachments) + return proxy.NewProxy(invoker, callBack, attachments) } + +// GetInvoker ... func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker { return &ProxyInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), } } +// ProxyInvoker ... type ProxyInvoker struct { protocol.BaseInvoker } -func (pi *ProxyInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { result := &protocol.RPCResult{} result.SetAttachments(invocation.Attachments()) @@ -99,7 +113,7 @@ func (pi *ProxyInvoker) Invoke(invocation protocol.Invocation) protocol.Result { in := []reflect.Value{svc.Rcvr()} if method.CtxType() != nil { - in = append(in, method.SuiteContext(nil)) // todo: ctx will be used later. + in = append(in, method.SuiteContext(ctx)) } // prepare argv diff --git a/common/proxy/proxy_factory/default_test.go b/common/proxy/proxy_factory/default_test.go index b6a6b675baf992b2d64ffd19291ee2dc009bd1e3..7159b4b00eb2fcddb0f20f701f56b3179e57c4a0 100644 --- a/common/proxy/proxy_factory/default_test.go +++ b/common/proxy/proxy_factory/default_test.go @@ -18,6 +18,7 @@ package proxy_factory import ( + "fmt" "testing" ) @@ -37,6 +38,21 @@ func Test_GetProxy(t *testing.T) { assert.NotNil(t, proxy) } +type TestAsync struct { +} + +func (u *TestAsync) CallBack(res common.CallbackResponse) { + fmt.Println("CallBack res:", res) +} + +func Test_GetAsyncProxy(t *testing.T) { + proxyFactory := NewDefaultProxyFactory() + url := common.NewURLWithOptions() + async := &TestAsync{} + proxy := proxyFactory.GetAsyncProxy(protocol.NewBaseInvoker(*url), async.CallBack, url) + assert.NotNil(t, proxy) +} + func Test_GetInvoker(t *testing.T) { proxyFactory := NewDefaultProxyFactory() url := common.NewURLWithOptions() diff --git a/common/rpc_service.go b/common/rpc_service.go index 4741a6fa3c0daef97f044f639a5e64a38fe4a187..b235c32abc9a971d7144605c8b4b82953ac8f3c4 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -34,22 +34,41 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -// rpc service interface +// RPCService +//rpc service interface type RPCService interface { - Reference() string // rpc service id or reference id + // Reference: + // rpc service id or reference id + Reference() string } +//AsyncCallbackService callback interface for async +type AsyncCallbackService interface { + // Callback: callback + CallBack(response CallbackResponse) +} + +//CallbackResponse for different protocol +type CallbackResponse interface{} + +//AsyncCallback async callback method +type AsyncCallback func(response CallbackResponse) + // for lowercase func // func MethodMapper() map[string][string] { // return map[string][string]{} // } -const METHOD_MAPPER = "MethodMapper" +const ( + // METHOD_MAPPER ... + METHOD_MAPPER = "MethodMapper" +) var ( // Precompute the reflect type for error. Can't use error directly // because Typeof takes an empty interface value. This is annoying. typeOfError = reflect.TypeOf((*error)(nil)).Elem() + // ServiceMap ... // todo: lowerecas? ServiceMap = &serviceMap{ serviceMap: make(map[string]map[string]*Service), @@ -60,6 +79,7 @@ var ( // info of method ////////////////////////// +// MethodType ... type MethodType struct { method reflect.Method ctxType reflect.Type // request context @@ -67,18 +87,27 @@ type MethodType struct { replyType reflect.Type // return value, otherwise it is nil } +// Method ... func (m *MethodType) Method() reflect.Method { return m.method } + +// CtxType ... func (m *MethodType) CtxType() reflect.Type { return m.ctxType } + +// ArgsType ... func (m *MethodType) ArgsType() []reflect.Type { return m.argsType } + +// ReplyType ... func (m *MethodType) ReplyType() reflect.Type { return m.replyType } + +// SuiteContext ... func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value { if contextv := reflect.ValueOf(ctx); contextv.IsValid() { return contextv @@ -90,6 +119,7 @@ func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value { // info of service interface ////////////////////////// +// Service ... type Service struct { name string rcvr reflect.Value @@ -97,12 +127,17 @@ type Service struct { methods map[string]*MethodType } +// Method ... func (s *Service) Method() map[string]*MethodType { return s.methods } + +// RcvrType ... func (s *Service) RcvrType() reflect.Type { return s.rcvrType } + +// Rcvr ... func (s *Service) Rcvr() reflect.Value { return s.rcvr } @@ -198,8 +233,8 @@ func (sm *serviceMap) UnRegister(protocol, serviceId string) error { // Is this an exported - upper case - name func isExported(name string) bool { - rune, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(rune) + s, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(s) } // Is this type exported or a builtin? diff --git a/common/rpc_service_test.go b/common/rpc_service_test.go index 7df039b905d3cc064c5d6d9404fc874cf693dac9..8c9b9d15cdd4061dbe2f445b5fff7a868e5ae67e 100644 --- a/common/rpc_service_test.go +++ b/common/rpc_service_test.go @@ -122,9 +122,8 @@ func TestServiceMap_UnRegister(t *testing.T) { func TestMethodType_SuiteContext(t *testing.T) { mt := &MethodType{ctxType: reflect.TypeOf(context.TODO())} - c := context.TODO() - c = context.WithValue(c, "key", "value") - assert.Equal(t, reflect.ValueOf(c), mt.SuiteContext(c)) + ctx := context.WithValue(context.Background(), "key", "value") + assert.Equal(t, reflect.ValueOf(ctx), mt.SuiteContext(ctx)) assert.Equal(t, reflect.Zero(mt.ctxType), mt.SuiteContext(nil)) } diff --git a/common/url.go b/common/url.go index 6e7a843c8f4d2d3b24caf50983baf041e2dd036d..ebb648db27c3efff534f0d0a545f2211f335aa89 100644 --- a/common/url.go +++ b/common/url.go @@ -19,7 +19,6 @@ package common import ( "bytes" - "context" "encoding/base64" "fmt" "math" @@ -31,7 +30,7 @@ import ( ) import ( - "github.com/dubbogo/gost/container/gxset" + gxset "github.com/dubbogo/gost/container/set" "github.com/jinzhu/copier" perrors "github.com/pkg/errors" "github.com/satori/go.uuid" @@ -45,24 +44,33 @@ import ( // dubbo role type ///////////////////////////////// +// role constant const ( + // CONSUMER ... CONSUMER = iota + // CONFIGURATOR ... CONFIGURATOR + // ROUTER ... ROUTER + // PROVIDER ... PROVIDER ) var ( + // DubboNodes ... DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"} - DubboRole = [...]string{"consumer", "", "", "provider"} + // DubboRole Dubbo service role + DubboRole = [...]string{"consumer", "", "routers", "provider"} ) +// RoleType ... type RoleType int func (t RoleType) String() string { return DubboNodes[t] } +// Role ... func (t RoleType) Role() string { return DubboRole[t] } @@ -76,9 +84,9 @@ type baseUrl struct { paramsLock sync.RWMutex params url.Values PrimitiveURL string - ctx context.Context } +// URL ... type URL struct { baseUrl Path string // like /com.ikurento.dubbo.UserProvider3 @@ -91,66 +99,77 @@ type URL struct { type option func(*URL) +// WithUsername ... func WithUsername(username string) option { return func(url *URL) { url.Username = username } } +// WithPassword ... func WithPassword(pwd string) option { return func(url *URL) { url.Password = pwd } } +// WithMethods ... func WithMethods(methods []string) option { return func(url *URL) { url.Methods = methods } } +// WithParams ... func WithParams(params url.Values) option { return func(url *URL) { url.params = params } } +// WithParamsValue ... func WithParamsValue(key, val string) option { return func(url *URL) { url.SetParam(key, val) } } +// WithProtocol ... func WithProtocol(proto string) option { return func(url *URL) { url.Protocol = proto } } +// WithIp ... func WithIp(ip string) option { return func(url *URL) { url.Ip = ip } } +// WithPort ... func WithPort(port string) option { return func(url *URL) { url.Port = port } } +// WithPath ... func WithPath(path string) option { return func(url *URL) { url.Path = "/" + strings.TrimPrefix(path, "/") } } +// WithLocation ... func WithLocation(location string) option { return func(url *URL) { url.Location = location } } +// WithToken ... func WithToken(token string) option { return func(url *URL) { if len(token) > 0 { @@ -163,6 +182,7 @@ func WithToken(token string) option { } } +// NewURLWithOptions ... func NewURLWithOptions(opts ...option) *URL { url := &URL{} for _, opt := range opts { @@ -172,13 +192,15 @@ func NewURLWithOptions(opts ...option) *URL { return url } -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 @@ -193,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) } @@ -218,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 { @@ -227,6 +249,7 @@ func NewURL(ctx context.Context, urlString string, opts ...option) (URL, error) return s, nil } +// URLEqual ... func (c URL) URLEqual(url URL) bool { c.Ip = "" c.Port = "" @@ -254,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 @@ -265,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 { @@ -282,6 +307,7 @@ func (c URL) String() string { return buildString } +// Key ... func (c URL) Key() string { buildString := fmt.Sprintf( "%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s", @@ -290,6 +316,7 @@ func (c URL) Key() string { //return c.ServiceKey() } +// ServiceKey ... func (c URL) ServiceKey() string { intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if intf == "" { @@ -313,40 +340,63 @@ func (c URL) ServiceKey() string { return buf.String() } +// ColonSeparatedKey +// The format is "{interface}:[version]:[group]" +func (c *URL) ColonSeparatedKey() string { + intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) + if intf == "" { + return "" + } + buf := &bytes.Buffer{} + buf.WriteString(intf) + buf.WriteString(":") + version := c.GetParam(constant.VERSION_KEY, "") + if version != "" && version != "0.0.0" { + buf.WriteString(version) + } + group := c.GetParam(constant.GROUP_KEY, "") + buf.WriteString(":") + if group != "" { + buf.WriteString(group) + } + return buf.String() +} + +// EncodedServiceKey ... func (c *URL) EncodedServiceKey() string { serviceKey := c.ServiceKey() return strings.Replace(serviceKey, "/", "*", 1) } -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, "/")) if service != "" { 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 } } return "" } +// AddParam ... func (c *URL) AddParam(key string, value string) { c.paramsLock.Lock() c.params.Add(key, value) c.paramsLock.Unlock() } +// SetParam ... func (c *URL) SetParam(key string, value string) { c.paramsLock.Lock() c.params.Set(key, value) c.paramsLock.Unlock() } +// RangeParams ... func (c *URL) RangeParams(f func(key, value string) bool) { c.paramsLock.RLock() defer c.paramsLock.RUnlock() @@ -357,6 +407,7 @@ func (c *URL) RangeParams(f func(key, value string) bool) { } } +// GetParam ... func (c URL) GetParam(s string, d string) string { var r string c.paramsLock.RLock() @@ -367,10 +418,12 @@ func (c URL) GetParam(s string, d string) string { return r } +// GetParams ... func (c URL) GetParams() url.Values { return c.params } +// GetParamAndDecoded ... func (c URL) GetParamAndDecoded(key string) (string, error) { c.paramsLock.RLock() defer c.paramsLock.RUnlock() @@ -379,6 +432,7 @@ func (c URL) GetParamAndDecoded(key string) (string, error) { return value, err } +// GetRawParam ... func (c URL) GetRawParam(key string) string { switch key { case "protocol": @@ -398,7 +452,7 @@ func (c URL) GetRawParam(key string) string { } } -// GetParamBool +// GetParamBool ... func (c URL) GetParamBool(s string, d bool) bool { var r bool @@ -409,6 +463,7 @@ func (c URL) GetParamBool(s string, d bool) bool { return r } +// GetParamInt ... func (c URL) GetParamInt(s string, d int64) int64 { var r int var err error @@ -419,6 +474,7 @@ func (c URL) GetParamInt(s string, d int64) int64 { return int64(r) } +// GetMethodParamInt ... func (c URL) GetMethodParamInt(method string, key string, d int64) int64 { var r int var err error @@ -430,6 +486,7 @@ func (c URL) GetMethodParamInt(method string, key string, d int64) int64 { return int64(r) } +// GetMethodParamInt64 ... func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { r := c.GetMethodParamInt(method, key, math.MinInt64) if r == math.MinInt64 { @@ -439,6 +496,7 @@ func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { return r } +// GetMethodParam ... func (c URL) GetMethodParam(method string, key string, d string) string { var r string if r = c.GetParam("methods."+method+"."+key, ""); r == "" { @@ -447,6 +505,13 @@ func (c URL) GetMethodParam(method string, key string, d string) string { return r } +// GetMethodParamBool ... +func (c URL) GetMethodParamBool(method string, key string, d bool) bool { + r := c.GetParamBool("methods."+method+"."+key, d) + return r +} + +// RemoveParams ... func (c *URL) RemoveParams(set *gxset.HashSet) { c.paramsLock.Lock() defer c.paramsLock.Unlock() @@ -456,6 +521,7 @@ func (c *URL) RemoveParams(set *gxset.HashSet) { } } +// SetParams ... func (c *URL) SetParams(m url.Values) { for k := range m { c.SetParam(k, m.Get(k)) @@ -507,6 +573,7 @@ func (c URL) ToMap() map[string]string { // in this function we should merge the reference local url config into the service url from registry. //TODO configuration merge, in the future , the configuration center's config should merge too. +// MergeUrl ... func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { mergedUrl := serviceUrl.Clone() @@ -518,7 +585,7 @@ func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { return true }) //loadBalance,cluster,retries strategy config - methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY}) + methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY}) //remote timestamp if v := serviceUrl.GetParam(constant.TIMESTAMP_KEY, ""); len(v) > 0 { @@ -535,6 +602,8 @@ func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { return mergedUrl } + +// Clone ... func (c *URL) Clone() *URL { newUrl := &URL{} copier.Copy(newUrl, c) diff --git a/common/url_test.go b/common/url_test.go index 41fd374a4d8a4ad3e15de1080fe46d426620909f..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" @@ -217,6 +218,18 @@ func TestURL_GetMethodParam(t *testing.T) { assert.Equal(t, "1s", v) } +func TestURL_GetMethodParamBool(t *testing.T) { + params := url.Values{} + params.Set("methods.GetValue.async", "true") + u := URL{baseUrl: baseUrl{params: params}} + v := u.GetMethodParamBool("GetValue", "async", false) + assert.Equal(t, true, v) + + u = URL{} + v = u.GetMethodParamBool("GetValue2", "async", false) + assert.Equal(t, false, v) +} + func TestMergeUrl(t *testing.T) { referenceUrlParams := url.Values{} referenceUrlParams.Set(constant.CLUSTER_KEY, "random") @@ -227,20 +240,20 @@ func TestMergeUrl(t *testing.T) { serviceUrlParams.Set("test2", "1") serviceUrlParams.Set(constant.CLUSTER_KEY, "roundrobin") serviceUrlParams.Set(constant.RETRIES_KEY, "2") - serviceUrlParams.Set("methods.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)) + serviceUrlParams.Set(constant.METHOD_KEYS+".testMethod."+constant.RETRIES_KEY, "2") + 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, "")) assert.Equal(t, "1", mergedUrl.GetParam("test2", "")) assert.Equal(t, "1", mergedUrl.GetParam("test3", "")) assert.Equal(t, "1", mergedUrl.GetParam(constant.RETRIES_KEY, "")) - assert.Equal(t, "1", mergedUrl.GetParam("methods.testMethod."+constant.RETRIES_KEY, "")) + assert.Equal(t, "2", mergedUrl.GetParam(constant.METHOD_KEYS+".testMethod."+constant.RETRIES_KEY, "")) } 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") @@ -250,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") @@ -259,3 +272,17 @@ func TestClone(t *testing.T) { assert.Equal(t, u1.Protocol, "dubbo") assert.Equal(t, u2.Protocol, "provider") } + +func TestColonSeparatedKey(t *testing.T) { + 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, "")+"::") + u1.AddParam(constant.VERSION_KEY, "version1") + assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+":version1:") + u1.AddParam(constant.GROUP_KEY, "group1") + assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+":version1:group1") + u1.SetParam(constant.VERSION_KEY, "") + assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+"::group1") + +} diff --git a/config/application_config.go b/config/application_config.go index fcd4d38c9b55963c32d58fdd1b80375083a76d8c..23ab7d34aceaba02d7f592906d6f4e3d6cf36dae 100644 --- a/config/application_config.go +++ b/config/application_config.go @@ -25,6 +25,7 @@ import ( "github.com/apache/dubbo-go/common/constant" ) +// ApplicationConfig ... type ApplicationConfig struct { Organization string `yaml:"organization" json:"organization,omitempty" property:"organization"` Name string `yaml:"name" json:"name,omitempty" property:"name"` @@ -34,15 +35,22 @@ type ApplicationConfig struct { Environment string `yaml:"environment" json:"environment,omitempty" property:"environment"` } +// Prefix ... func (*ApplicationConfig) Prefix() string { return constant.DUBBO + ".application." } + +// Id ... func (c *ApplicationConfig) Id() string { return "" } + +// SetId ... func (c *ApplicationConfig) SetId(id string) { } + +// UnmarshalYAML ... func (c *ApplicationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err diff --git a/config/base_config.go b/config/base_config.go index 264eeda3cc20da1b097a24dc35cf4f9b2291eeeb..6d5ec7e2498ba65b2a6833b6c9cefcb3394e60df 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -14,10 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package config import ( - "context" + "io/ioutil" + "path" "reflect" "strconv" "strings" @@ -25,6 +27,7 @@ import ( import ( perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" ) import ( @@ -39,15 +42,21 @@ type multiConfiger interface { Prefix() string } +// BaseConfig is the common configuration for provider and consumer type BaseConfig struct { ConfigCenterConfig *ConfigCenterConfig `yaml:"config_center" json:"config_center,omitempty"` configCenterUrl *common.URL prefix string fatherConfig interface{} + + 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)) +// 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 } @@ -68,7 +77,7 @@ func (c *BaseConfig) prepareEnvironment() error { logger.Errorf("Get dynamic configuration error , error message is %v", err) return perrors.WithStack(err) } - content, err := dynamicConfig.GetConfig(c.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) + content, err := dynamicConfig.GetProperties(c.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group)) if err != nil { logger.Errorf("Get config content in dynamic configuration error , error message is %v", err) return perrors.WithStack(err) @@ -88,7 +97,10 @@ func (c *BaseConfig) prepareEnvironment() error { if len(configFile) == 0 { configFile = c.ConfigCenterConfig.ConfigFile } - appContent, err = dynamicConfig.GetConfig(configFile, config_center.WithGroup(appGroup)) + appContent, err = dynamicConfig.GetProperties(configFile, config_center.WithGroup(appGroup)) + if err != nil { + return perrors.WithStack(err) + } } //global config file mapContent, err := dynamicConfig.Parser().Parse(content) @@ -129,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() @@ -138,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 != "" { @@ -291,11 +305,12 @@ 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() { - config := element.Value.(*config.InmemoryConfiguration) - c.freshInternalConfig(config) + cfg := element.Value.(*config.InmemoryConfiguration) + c.freshInternalConfig(cfg) } } @@ -308,6 +323,7 @@ func (c *BaseConfig) freshInternalConfig(config *config.InmemoryConfiguration) { setFieldValue(val, reflect.Value{}, config) } +// SetFatherConfig ... func (c *BaseConfig) SetFatherConfig(fatherConfig interface{}) { c.fatherConfig = fatherConfig } @@ -350,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 6dc3749e55f7efbfb1177079f613360cd0d4cc33..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" ) @@ -29,6 +29,7 @@ import ( "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/config_center/apollo" ) func Test_refresh(t *testing.T) { @@ -39,6 +40,7 @@ func Test_refresh(t *testing.T) { mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10" mockMap["dubbo.consumer.check"] = "false" mockMap["dubbo.application.name"] = "dubbo" + mockMap["dubbo.shutdown.timeout"] = "12s" config.GetEnvInstance().UpdateExternalConfigMap(mockMap) @@ -113,6 +115,13 @@ func Test_refresh(t *testing.T) { }, }, }, + ShutdownConfig: &ShutdownConfig{ + Timeout: "12s", + StepTimeout: "2s", + RejectRequestHandler: "mock", + RejectRequest: false, + RequestsFinished: false, + }, } c.SetFatherConfig(father) @@ -483,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) @@ -509,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_center_config.go b/config/config_center_config.go index ed43558956a181e669a1a8936182b65a2fb2766c..40b9b6517186a8a4f7956db3d23f0a1cdfbdc8cb 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -19,6 +19,7 @@ package config import ( "context" + "net/url" "time" ) @@ -26,6 +27,11 @@ import ( "github.com/creasty/defaults" ) +import ( + "github.com/apache/dubbo-go/common/constant" +) + +// ConfigCenterConfig ... type ConfigCenterConfig struct { context context.Context Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` @@ -35,11 +41,14 @@ type ConfigCenterConfig struct { Username string `yaml:"username" json:"username,omitempty"` Password string `yaml:"password" json:"password,omitempty"` ConfigFile string `default:"dubbo.properties" yaml:"config_file" json:"config_file,omitempty"` + Namespace string `default:"dubbo" yaml:"namespace" json:"namespace,omitempty"` AppConfigFile string `default:"dubbo.properties" yaml:"app_config_file" json:"app_config_file,omitempty"` + AppId string `default:"dubbo" yaml:"app_id" json:"app_id,omitempty"` TimeoutStr string `yaml:"timeout" json:"timeout,omitempty"` timeout time.Duration } +// UnmarshalYAML ... func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -50,3 +59,13 @@ func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) er } return nil } + +// GetUrlMap ... +func (c *ConfigCenterConfig) GetUrlMap() url.Values { + urlMap := url.Values{} + urlMap.Set(constant.CONFIG_NAMESPACE_KEY, c.Namespace) + urlMap.Set(constant.CONFIG_GROUP_KEY, c.Group) + urlMap.Set(constant.CONFIG_CLUSTER_KEY, c.Cluster) + urlMap.Set(constant.CONFIG_APP_ID_KEY, c.AppId) + return urlMap +} diff --git a/config/config_loader.go b/config/config_loader.go index b737d3f233700f596469cfd678aa7ae7f9a82b85..437f4d7323e66afcf62808b3c8d6bf51cc5bce88 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -31,24 +31,31 @@ import ( ) var ( - consumerConfig *ConsumerConfig - providerConfig *ProviderConfig - maxWait = 3 + consumerConfig *ConsumerConfig + providerConfig *ProviderConfig + 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 @@ -69,12 +76,23 @@ func checkApplicationName(config *ApplicationConfig) { } } -// Dubbo Init +// 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!") } else { + + metricConfig = consumerConfig.MetricConfig + applicationConfig = consumerConfig.ApplicationConfig + checkApplicationName(consumerConfig.ApplicationConfig) if err := configCenterRefreshConsumer(); err != nil { logger.Errorf("[consumer config center refresh] %#v", err) @@ -91,9 +109,10 @@ func Load() { continue } ref.id = key - ref.Refer() + ref.Refer(rpcService) ref.Implement(rpcService) } + //wait for invoker is available, if wait over default 3s, then panic var count int checkok := true @@ -131,6 +150,11 @@ func Load() { if providerConfig == nil { logger.Warnf("providerConfig is nil!") } else { + + // so, you should know that the consumer's config will be override + metricConfig = providerConfig.MetricConfig + applicationConfig = providerConfig.ApplicationConfig + checkApplicationName(providerConfig.ApplicationConfig) if err := configCenterRefreshProvider(); err != nil { logger.Errorf("[provider config center refresh] %#v", err) @@ -149,14 +173,55 @@ func Load() { } } } + // init the shutdown callback + GracefulShutdownInit() } -// get rpc service for consumer +// GetRPCService get rpc service for consumer func GetRPCService(name string) common.RPCService { return consumerConfig.References[name].GetRPCService() } -// create rpc service for consumer +// RPCService create rpc service for consumer func RPCService(service common.RPCService) { consumerConfig.References[service.Reference()].Implement(service) } + +// GetMetricConfig find the MetricConfig +// if it is nil, create a new one +func GetMetricConfig() *MetricConfig { + if metricConfig == nil { + metricConfig = &MetricConfig{} + } + return metricConfig +} + +// GetApplicationConfig find the application config +// if not, we will create one +// Usually applicationConfig will be initialized when system start +func GetApplicationConfig() *ApplicationConfig { + if applicationConfig == nil { + applicationConfig = &ApplicationConfig{} + } + return applicationConfig +} + +// GetProviderConfig find the provider config +// if not found, create new one +func GetProviderConfig() ProviderConfig { + if providerConfig == nil { + logger.Warnf("providerConfig is nil!") + return ProviderConfig{} + } + return *providerConfig +} + +// GetConsumerConfig find the consumer config +// if not found, create new one +func GetConsumerConfig() ConsumerConfig { + if consumerConfig == nil { + logger.Warnf("consumerConfig is nil!") + return ConsumerConfig{} + } + return *consumerConfig +} diff --git a/config/consumer_config.go b/config/consumer_config.go index b1ebdd5d8e082bf836071460e2a330632e07335c..94da301ce45acedb720120d56dc07bf76c780d7f 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -14,19 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package config import ( - "context" - "io/ioutil" - "path" "time" ) import ( "github.com/creasty/defaults" + "github.com/dubbogo/getty" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( @@ -38,6 +36,7 @@ import ( // consumerConfig ///////////////////////// +// ConsumerConfig ... type ConsumerConfig struct { BaseConfig `yaml:",inline"` Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` @@ -52,13 +51,15 @@ type ConsumerConfig struct { ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` Check *bool `yaml:"check" json:"check,omitempty" property:"check"` - Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` - Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"` - References map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"` - ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` - FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` + Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` + Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"` + References map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"` + ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` + FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` + ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` } +// UnmarshalYAML ... func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -70,37 +71,24 @@ func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } +// Prefix ... func (*ConsumerConfig) Prefix() string { return constant.ConsumerConfigPrefix } +// SetConsumerConfig ... func SetConsumerConfig(c ConsumerConfig) { consumerConfig = &c } -func GetConsumerConfig() ConsumerConfig { - if consumerConfig == nil { - logger.Warnf("consumerConfig is nil!") - return ConsumerConfig{} - } - return *consumerConfig -} - +// ConsumerInit ... func ConsumerInit(confConFile string) error { if confConFile == "" { 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)) } @@ -117,6 +105,10 @@ func ConsumerInit(confConFile string) error { if consumerConfig.RequestTimeout, err = time.ParseDuration(consumerConfig.Request_Timeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(Request_Timeout{%#v})", consumerConfig.Request_Timeout) } + if consumerConfig.RequestTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + return perrors.WithMessagef(err, "request_timeout %s should be less than %s", + consumerConfig.Request_Timeout, time.Duration(getty.MaxWheelTimeSpan)) + } } if consumerConfig.Connect_Timeout != "" { if consumerConfig.ConnectTimeout, err = time.ParseDuration(consumerConfig.Connect_Timeout); err != nil { @@ -132,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/generic_service.go b/config/generic_service.go index 8a4e88df9788554bc4a5ee33884166e4ccede37f..9895486e977a9848e576597f31b724d51d144d4e 100644 --- a/config/generic_service.go +++ b/config/generic_service.go @@ -14,17 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package config +// GenericService ... type GenericService struct { Invoke func(req []interface{}) (interface{}, error) `dubbo:"$invoke"` referenceStr string } +// NewGenericService ... func NewGenericService(referenceStr string) *GenericService { return &GenericService{referenceStr: referenceStr} } +// Reference ... func (u *GenericService) Reference() string { return u.referenceStr } diff --git a/config/graceful_shutdown.go b/config/graceful_shutdown.go new file mode 100644 index 0000000000000000000000000000000000000000..382f05c8d57c4363108873433fd03565d03b9a50 --- /dev/null +++ b/config/graceful_shutdown.go @@ -0,0 +1,231 @@ +/* + * 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 ( + "os" + "os/signal" + "runtime/debug" + "time" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" +) + +/* + * The key point is that find out the signals to handle. + * The most important documentation is https://golang.org/pkg/os/signal/ + * From this documentation, we can know that: + * 1. The signals SIGKILL and SIGSTOP may not be caught by signal package; + * 2. SIGHUP, SIGINT, or SIGTERM signal causes the program to exit + * 3. SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, or SIGSYS signal causes the program to exit with a stack dump + * 4. The invocation of Notify(signal...) will disable the default behavior of those signals. + * + * So the signals SIGKILL, SIGSTOP, SIGHUP, SIGINT, SIGTERM, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, SIGSYS + * should be processed. + * syscall.SIGEMT cannot be found in CI + * It's seems that the Unix/Linux does not have the signal SIGSTKFLT. https://github.com/golang/go/issues/33381 + * So this signal will be ignored. + * The signals are different on different platforms. + * We define them by using 'package build' feature https://golang.org/pkg/go/build/ + */ + +// GracefulShutdownInit ... +func GracefulShutdownInit() { + + signals := make(chan os.Signal, 1) + + signal.Notify(signals, ShutdownSignals...) + + go func() { + select { + case sig := <-signals: + logger.Infof("get signal %s, application will shutdown.", sig) + // gracefulShutdownOnce.Do(func() { + BeforeShutdown() + + // those signals' original behavior is exit with dump ths stack, so we try to keep the behavior + for _, dumpSignal := range DumpHeapShutdownSignals { + if sig == dumpSignal { + debug.WriteHeapDump(os.Stdout.Fd()) + } + } + + time.AfterFunc(totalTimeout(), func() { + logger.Warn("Shutdown gracefully timeout, application will shutdown immediately. ") + os.Exit(0) + }) + + os.Exit(0) + } + }() +} + +// BeforeShutdown ... +func BeforeShutdown() { + + destroyAllRegistries() + // waiting for a short time so that the clients have enough time to get the notification that server shutdowns + // The value of configuration depends on how long the clients will get notification. + waitAndAcceptNewRequests() + + // reject the new request, but keeping waiting for accepting requests + waitForReceivingRequests() + + // we fetch the protocols from Consumer.References. Consumer.ProtocolConfig doesn't contains all protocol, like jsonrpc + consumerProtocols := getConsumerProtocols() + + // If this application is not the provider, it will do nothing + destroyProviderProtocols(consumerProtocols) + + // reject sending the new request, and waiting for response of sending requests + waitForSendingRequests() + + // If this application is not the consumer, it will do nothing + destroyConsumerProtocols(consumerProtocols) + + logger.Info("Graceful shutdown --- Execute the custom callbacks.") + customCallbacks := extension.GetAllCustomShutdownCallbacks() + for callback := customCallbacks.Front(); callback != nil; callback = callback.Next() { + callback.Value.(func())() + } +} + +func destroyAllRegistries() { + logger.Info("Graceful shutdown --- Destroy all registries. ") + registryProtocol := extension.GetProtocol(constant.REGISTRY_KEY) + registryProtocol.Destroy() +} + +func destroyConsumerProtocols(consumerProtocols *gxset.HashSet) { + logger.Info("Graceful shutdown --- Destroy consumer's protocols. ") + for name := range consumerProtocols.Items { + extension.GetProtocol(name.(string)).Destroy() + } +} + +/** + * destroy the provider's protocol. + * if the protocol is consumer's protocol too, we will keep it. + */ +func destroyProviderProtocols(consumerProtocols *gxset.HashSet) { + + logger.Info("Graceful shutdown --- Destroy provider's protocols. ") + + if providerConfig == nil || providerConfig.Protocols == nil { + return + } + + for _, protocol := range providerConfig.Protocols { + + // the protocol is the consumer's protocol too, we can not destroy it. + if consumerProtocols.Contains(protocol.Name) { + continue + } + extension.GetProtocol(protocol.Name).Destroy() + } +} + +func waitAndAcceptNewRequests() { + + logger.Info("Graceful shutdown --- Keep waiting and accept new requests for a short time. ") + if providerConfig == nil || providerConfig.ShutdownConfig == nil { + return + } + + timeout := providerConfig.ShutdownConfig.GetStepTimeout() + + // ignore this step + if timeout < 0 { + return + } + time.Sleep(timeout) +} + +// for provider. It will wait for processing receiving requests +func waitForReceivingRequests() { + logger.Info("Graceful shutdown --- Keep waiting until accepting requests finish or timeout. ") + if providerConfig == nil || providerConfig.ShutdownConfig == nil { + // ignore this step + return + } + waitingProcessedTimeout(providerConfig.ShutdownConfig) +} + +// for consumer. It will wait for the response of sending requests +func waitForSendingRequests() { + logger.Info("Graceful shutdown --- Keep waiting until sending requests getting response or timeout ") + if consumerConfig == nil || consumerConfig.ShutdownConfig == nil { + // ignore this step + return + } + waitingProcessedTimeout(consumerConfig.ShutdownConfig) +} + +func waitingProcessedTimeout(shutdownConfig *ShutdownConfig) { + timeout := shutdownConfig.GetStepTimeout() + if timeout <= 0 { + return + } + start := time.Now() + + for time.Now().After(start.Add(timeout)) && !shutdownConfig.RequestsFinished { + // sleep 10 ms and then we check it again + time.Sleep(10 * time.Millisecond) + } +} + +func totalTimeout() time.Duration { + var providerShutdown time.Duration + if providerConfig != nil && providerConfig.ShutdownConfig != nil { + providerShutdown = providerConfig.ShutdownConfig.GetTimeout() + } + + var consumerShutdown time.Duration + if consumerConfig != nil && consumerConfig.ShutdownConfig != nil { + consumerShutdown = consumerConfig.ShutdownConfig.GetTimeout() + } + + var timeout = providerShutdown + if consumerShutdown > providerShutdown { + timeout = consumerShutdown + } + return timeout +} + +/* + * we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc. + */ +func getConsumerProtocols() *gxset.HashSet { + result := gxset.NewSet() + if consumerConfig == nil || consumerConfig.References == nil { + return result + } + + for _, reference := range consumerConfig.References { + result.Add(reference.Protocol) + } + return result +} diff --git a/config/graceful_shutdown_config.go b/config/graceful_shutdown_config.go new file mode 100644 index 0000000000000000000000000000000000000000..6bbabebf2538effcbbe4bddc50857acf5f962a61 --- /dev/null +++ b/config/graceful_shutdown_config.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 config + +import ( + "time" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" +) + +const ( + defaultTimeout = 60 * time.Second + defaultStepTimeout = 10 * time.Second +) + +// ShutdownConfig ... +type ShutdownConfig struct { + /* + * Total timeout. Even though we don't release all resources, + * the application will shutdown if the costing time is over this configuration. The unit is ms. + * default value is 60 * 1000 ms = 1 minutes + * In general, it should be bigger than 3 * StepTimeout. + */ + Timeout string `default:"60s" yaml:"timeout" json:"timeout,omitempty" property:"timeout"` + /* + * the timeout on each step. You should evaluate the response time of request + * and the time that client noticed that server shutdown. + * For example, if your client will received the notification within 10s when you start to close server, + * and the 99.9% requests will return response in 2s, so the StepTimeout will be bigger than(10+2) * 1000ms, + * maybe (10 + 2*3) * 1000ms is a good choice. + */ + StepTimeout string `default:"10s" yaml:"step_timeout" json:"step.timeout,omitempty" property:"step.timeout"` + // when we try to shutdown the application, we will reject the new requests. In most cases, you don't need to configure this. + RejectRequestHandler string `yaml:"reject_handler" json:"reject_handler,omitempty" property:"reject_handler"` + // true -> new request will be rejected. + RejectRequest bool + + // true -> all requests had been processed. In provider side it means that all requests are returned response to clients + // In consumer side, it means that all requests getting response from servers + RequestsFinished bool +} + +// Prefix ... +func (config *ShutdownConfig) Prefix() string { + return constant.ShutdownConfigPrefix +} + +// GetTimeout ... +func (config *ShutdownConfig) GetTimeout() time.Duration { + result, err := time.ParseDuration(config.Timeout) + if err != nil { + logger.Errorf("The Timeout configuration is invalid: %s, and we will use the default value: %s, err: %v", + config.Timeout, defaultTimeout.String(), err) + return defaultTimeout + } + return result +} + +// GetStepTimeout ... +func (config *ShutdownConfig) GetStepTimeout() time.Duration { + result, err := time.ParseDuration(config.StepTimeout) + if err != nil { + logger.Errorf("The StepTimeout configuration is invalid: %s, and we will use the default value: %s, err: %v", + config.StepTimeout, defaultStepTimeout.String(), err) + return defaultStepTimeout + } + return result +} diff --git a/config/graceful_shutdown_config_test.go b/config/graceful_shutdown_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..583ed70b838a8271a47e180ee3c6eb32cbb46984 --- /dev/null +++ b/config/graceful_shutdown_config_test.go @@ -0,0 +1,49 @@ +/* + * 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 ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestShutdownConfig_GetTimeout(t *testing.T) { + config := ShutdownConfig{} + assert.False(t, config.RejectRequest) + assert.False(t, config.RequestsFinished) + + config = ShutdownConfig{ + Timeout: "12x", + StepTimeout: "34a", + } + + assert.Equal(t, 60*time.Second, config.GetTimeout()) + assert.Equal(t, 10*time.Second, config.GetStepTimeout()) + + config = ShutdownConfig{ + Timeout: "34ms", + StepTimeout: "79ms", + } + + assert.Equal(t, 34*time.Millisecond, config.GetTimeout()) + assert.Equal(t, 79*time.Millisecond, config.GetStepTimeout()) +} diff --git a/config/graceful_shutdown_signal_darwin.go b/config/graceful_shutdown_signal_darwin.go new file mode 100644 index 0000000000000000000000000000000000000000..8ad79ffa62ceed4096c60bfb9139b7ff1586808e --- /dev/null +++ b/config/graceful_shutdown_signal_darwin.go @@ -0,0 +1,34 @@ +/* + * 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 ( + "os" + "syscall" +) + +var ( + // ShutdownSignals ... + ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, + syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, + syscall.SIGABRT, syscall.SIGSYS} + + // DumpHeapShutdownSignals ... + DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, + syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS} +) diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go new file mode 100644 index 0000000000000000000000000000000000000000..8ad79ffa62ceed4096c60bfb9139b7ff1586808e --- /dev/null +++ b/config/graceful_shutdown_signal_linux.go @@ -0,0 +1,34 @@ +/* + * 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 ( + "os" + "syscall" +) + +var ( + // ShutdownSignals ... + ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, + syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, + syscall.SIGABRT, syscall.SIGSYS} + + // DumpHeapShutdownSignals ... + DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, + syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS} +) diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..815a05ecb20a8fc202debaf6f39d699845cd689e --- /dev/null +++ b/config/graceful_shutdown_signal_windows.go @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "os" + "syscall" +) + +var ( + // ShutdownSignals ... + ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, + syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, + syscall.SIGABRT} + + // DumpHeapShutdownSignals ... + DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT} +) diff --git a/config/graceful_shutdown_test.go b/config/graceful_shutdown_test.go new file mode 100644 index 0000000000000000000000000000000000000000..de203572c76281d221181dea90b0f31b43038de6 --- /dev/null +++ b/config/graceful_shutdown_test.go @@ -0,0 +1,99 @@ +/* + * 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 ( + "testing" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" +) + +func TestGracefulShutdownInit(t *testing.T) { + GracefulShutdownInit() +} + +func TestBeforeShutdown(t *testing.T) { + extension.SetProtocol("registry", func() protocol.Protocol { + return &mockRegistryProtocol{} + }) + extension.SetProtocol(constant.DUBBO, func() protocol.Protocol { + return &mockRegistryProtocol{} + }) + + extension.SetProtocol("mock", func() protocol.Protocol { + return &mockRegistryProtocol{} + }) + + // protocolConfigs := make(map[interface{}]interface{}, 16) + consumerReferences := map[string]*ReferenceConfig{} + consumerReferences[constant.DUBBO] = &ReferenceConfig{ + Protocol: constant.DUBBO, + } + + // without configuration + BeforeShutdown() + + consumerConfig = &ConsumerConfig{ + References: consumerReferences, + ShutdownConfig: &ShutdownConfig{ + Timeout: "1", + StepTimeout: "1s", + }} + + providerProtocols := map[string]*ProtocolConfig{} + providerProtocols[constant.DUBBO] = &ProtocolConfig{ + Name: constant.DUBBO, + } + + providerProtocols["mock"] = &ProtocolConfig{ + Name: "mock", + } + + providerConfig = &ProviderConfig{ + ShutdownConfig: &ShutdownConfig{ + Timeout: "1", + StepTimeout: "1s", + }, + Protocols: providerProtocols, + } + // test destroy protocol + BeforeShutdown() + + providerConfig = &ProviderConfig{ + ShutdownConfig: &ShutdownConfig{ + Timeout: "1", + StepTimeout: "-1s", + }, + Protocols: providerProtocols, + } + + consumerConfig = &ConsumerConfig{ + References: consumerReferences, + ShutdownConfig: &ShutdownConfig{ + Timeout: "1", + StepTimeout: "-1s", + }, + } + + // test ignore steps + BeforeShutdown() +} diff --git a/config/method_config.go b/config/method_config.go index e3f0b1b01b5c6d753da216ecf906aee3bf305944..8f196d9e2c03071a663db03cb185fb9106d6484a 100644 --- a/config/method_config.go +++ b/config/method_config.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package config import ( @@ -24,6 +25,7 @@ import ( "github.com/apache/dubbo-go/common/constant" ) +// MethodConfig ... type MethodConfig struct { InterfaceId string InterfaceName string @@ -36,16 +38,20 @@ type MethodConfig struct { TpsLimitStrategy string `yaml:"tps.limit.strategy" json:"tps.limit.strategy,omitempty" property:"tps.limit.strategy"` ExecuteLimit string `yaml:"execute.limit" json:"execute.limit,omitempty" property:"execute.limit"` ExecuteLimitRejectedHandler string `yaml:"execute.limit.rejected.handler" json:"execute.limit.rejected.handler,omitempty" property:"execute.limit.rejected.handler"` + Sticky bool `yaml:"sticky" json:"sticky,omitempty" property:"sticky"` + RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"` } +// Prefix ... func (c *MethodConfig) Prefix() string { - if c.InterfaceId != "" { + if len(c.InterfaceId) != 0 { return constant.DUBBO + "." + c.InterfaceName + "." + c.InterfaceId + "." + c.Name + "." - } else { - return constant.DUBBO + "." + c.InterfaceName + "." + c.Name + "." } + + return constant.DUBBO + "." + c.InterfaceName + "." + c.Name + "." } +// UnmarshalYAML ... func (c *MethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err diff --git a/config/metric_config.go b/config/metric_config.go new file mode 100644 index 0000000000000000000000000000000000000000..73a3ca1cfe4f1461db2e225947dd13199b2ad55e --- /dev/null +++ b/config/metric_config.go @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +var ( + defaultHistogramBucket = []float64{10, 50, 100, 200, 500, 1000, 10000} +) + +// This is the config struct for all metrics implementation +type MetricConfig struct { + Reporters []string `yaml:"reporters" json:"reporters,omitempty"` + HistogramBucket []float64 `yaml:"histogram_bucket" json:"histogram_bucket,omitempty"` +} + +// find the histogram bucket +// if it's empty, the default value will be return +func (mc *MetricConfig) GetHistogramBucket() []float64 { + if len(mc.HistogramBucket) == 0 { + mc.HistogramBucket = defaultHistogramBucket + } + return mc.HistogramBucket +} diff --git a/config/metric_config_test.go b/config/metric_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fe9d2493f37c0bd563931f5acf133105d72d0e53 --- /dev/null +++ b/config/metric_config_test.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 config + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestGetMetricConfig(t *testing.T) { + empty := GetMetricConfig() + assert.NotNil(t, empty) +} diff --git a/config/mock_rpcservice.go b/config/mock_rpcservice.go index 64d431ffb6dfbc7e25a988c6093cf0ab5cbd2db5..6c43699128247bf0ec483eb83f879bf4c3b67a37 100644 --- a/config/mock_rpcservice.go +++ b/config/mock_rpcservice.go @@ -21,16 +21,20 @@ import ( "context" ) +// MockService ... type MockService struct{} +// Reference ... func (*MockService) Reference() string { return "MockService" } +// GetUser ... func (*MockService) GetUser(ctx context.Context, itf []interface{}, str *struct{}) error { return nil } +// GetUser1 ... func (*MockService) GetUser1(ctx context.Context, itf []interface{}, str *struct{}) error { return nil } diff --git a/config/protocol_config.go b/config/protocol_config.go index 2803456dbcd44211fb6deef883beb7f5dbbf54ad..33de976bc6f5bf7341ddcff8d51c505cf23bbccd 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package config import ( @@ -24,25 +25,26 @@ import ( "github.com/apache/dubbo-go/common/constant" ) +// ProtocolConfig ... type ProtocolConfig struct { Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` } +// Prefix ... func (c *ProtocolConfig) Prefix() string { return constant.ProtocolConfigPrefix } 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 00faa1d0ab1b65a7a39d7d3548e5b89b0f250aba..a36fd4d0a07c3203e53582cbf2f3442d880a3981 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -14,18 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package config -import ( - "context" - "io/ioutil" - "path" -) +package config import ( "github.com/creasty/defaults" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( @@ -37,6 +31,7 @@ import ( // providerConfig ///////////////////////// +// ProviderConfig ... type ProviderConfig struct { BaseConfig `yaml:",inline"` Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` @@ -49,8 +44,10 @@ type ProviderConfig struct { Protocols map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"` ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" ` FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` + ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` } +// UnmarshalYAML ... func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -62,36 +59,24 @@ func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } +// Prefix ... func (*ProviderConfig) Prefix() string { return constant.ProviderConfigPrefix } +// SetProviderConfig ... func SetProviderConfig(p ProviderConfig) { providerConfig = &p } -func GetProviderConfig() ProviderConfig { - if providerConfig == nil { - logger.Warnf("providerConfig is nil!") - return ProviderConfig{} - } - return *providerConfig -} +// ProviderInit ... func ProviderInit(confProFile string) error { if len(confProFile) == 0 { 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)) } @@ -106,6 +91,7 @@ func ProviderInit(confProFile string) error { } logger.Debugf("provider config{%#v}\n", providerConfig) + return nil } @@ -113,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 c63ac2ef28ff85d07b76ad0f5fef669d83bca3a5..7ce0013194f5c1a1d09e014a858433833aa07f0e 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -39,93 +39,103 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// ReferenceConfig ... type ReferenceConfig struct { - context context.Context - pxy *proxy.Proxy - id string - InterfaceName string `required:"true" yaml:"interface" json:"interface,omitempty" property:"interface"` - Check *bool `yaml:"check" json:"check,omitempty" property:"check"` - Url string `yaml:"url" json:"url,omitempty" property:"url"` - Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` - Protocol string `default:"dubbo" yaml:"protocol" json:"protocol,omitempty" property:"protocol"` - Registry string `yaml:"registry" json:"registry,omitempty" property:"registry"` - Cluster string `yaml:"cluster" json:"cluster,omitempty" property:"cluster"` - Loadbalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` - Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` - Group string `yaml:"group" json:"group,omitempty" property:"group"` - Version string `yaml:"version" json:"version,omitempty" property:"version"` - Methods []*MethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` - async bool `yaml:"async" json:"async,omitempty" property:"async"` - Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` - invoker protocol.Invoker - urls []*common.URL - Generic bool `yaml:"generic" json:"generic,omitempty" property:"generic"` + context context.Context + pxy *proxy.Proxy + id string + InterfaceName string `required:"true" yaml:"interface" json:"interface,omitempty" property:"interface"` + Check *bool `yaml:"check" json:"check,omitempty" property:"check"` + Url string `yaml:"url" json:"url,omitempty" property:"url"` + Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` + Protocol string `default:"dubbo" yaml:"protocol" json:"protocol,omitempty" property:"protocol"` + Registry string `yaml:"registry" json:"registry,omitempty" property:"registry"` + Cluster string `yaml:"cluster" json:"cluster,omitempty" property:"cluster"` + Loadbalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` + Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` + Group string `yaml:"group" json:"group,omitempty" property:"group"` + Version string `yaml:"version" json:"version,omitempty" property:"version"` + Methods []*MethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` + Async bool `yaml:"async" json:"async,omitempty" property:"async"` + Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` + invoker protocol.Invoker + urls []*common.URL + Generic bool `yaml:"generic" json:"generic,omitempty" property:"generic"` + Sticky bool `yaml:"sticky" json:"sticky,omitempty" property:"sticky"` + RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"` } +// Prefix ... func (c *ReferenceConfig) Prefix() string { return constant.ReferenceConfigPrefix + c.InterfaceName + "." } -// The only way to get a new ReferenceConfig +// NewReferenceConfig The only way to get a new ReferenceConfig func NewReferenceConfig(id string, ctx context.Context) *ReferenceConfig { return &ReferenceConfig{id: id, context: ctx} } -func (refconfig *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - +// 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 { return err } - *refconfig = ReferenceConfig(raw) - if err := defaults.Set(refconfig); err != nil { + *c = ReferenceConfig(raw) + if err := defaults.Set(c); err != nil { return err } return nil } -func (refconfig *ReferenceConfig) Refer() { - url := common.NewURLWithOptions(common.WithPath(refconfig.id), common.WithProtocol(refconfig.Protocol), common.WithParams(refconfig.getUrlMap())) - - //1. user specified URL, could be peer-to-peer address, or register center's address. - if refconfig.Url != "" { - urlStrings := gxstrings.RegSplit(refconfig.Url, "\\s*[;]+\\s*") +// Refer ... +func (c *ReferenceConfig) Refer(_ interface{}) { + cfgURL := common.NewURLWithOptions( + common.WithPath(c.id), + common.WithProtocol(c.Protocol), + common.WithParams(c.getUrlMap()), + common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), + ) + + 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())) } if serviceUrl.Protocol == constant.REGISTRY_PROTOCOL { - serviceUrl.SubURL = url - refconfig.urls = append(refconfig.urls, &serviceUrl) + serviceUrl.SubURL = cfgURL + c.urls = append(c.urls, &serviceUrl) } else { if serviceUrl.Path == "" { - serviceUrl.Path = "/" + refconfig.id + serviceUrl.Path = "/" + c.id } // merge url need to do - newUrl := common.MergeUrl(&serviceUrl, url) - refconfig.urls = append(refconfig.urls, newUrl) + newUrl := common.MergeUrl(&serviceUrl, cfgURL) + c.urls = append(c.urls, newUrl) } - } } else { - //2. assemble SubURL from register center's configuration妯″紡 - refconfig.urls = loadRegistries(refconfig.Registry, consumerConfig.Registries, common.CONSUMER) + // 2. assemble SubURL from register center's configuration mode + c.urls = loadRegistries(c.Registry, consumerConfig.Registries, common.CONSUMER) - //set url to regUrls - for _, regUrl := range refconfig.urls { - regUrl.SubURL = url + // set url to regUrls + for _, regUrl := range c.urls { + regUrl.SubURL = cfgURL } } - if len(refconfig.urls) == 1 { - refconfig.invoker = extension.GetProtocol(refconfig.urls[0].Protocol).Refer(*refconfig.urls[0]) + + 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 refconfig.urls { + for _, u := range c.urls { invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(*u)) if u.Protocol == constant.REGISTRY_PROTOCOL { regUrl = u @@ -133,43 +143,54 @@ func (refconfig *ReferenceConfig) Refer() { } if regUrl != nil { cluster := extension.GetCluster("registryAware") - refconfig.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) + c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } else { - cluster := extension.GetCluster(refconfig.Cluster) - refconfig.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) + cluster := extension.GetCluster(c.Cluster) + c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) } } - //create proxy - refconfig.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetProxy(refconfig.invoker, url) + // create proxy + if c.Async { + callback := GetCallback(c.id) + c.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetAsyncProxy(c.invoker, callback, cfgURL) + } else { + c.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetProxy(c.invoker, cfgURL) + } } +// Implement // @v is service provider implemented RPCService -func (refconfig *ReferenceConfig) Implement(v common.RPCService) { - refconfig.pxy.Implement(v) +func (c *ReferenceConfig) Implement(v common.RPCService) { + c.pxy.Implement(v) } -func (refconfig *ReferenceConfig) GetRPCService() common.RPCService { - return refconfig.pxy.Get() +// GetRPCService ... +func (c *ReferenceConfig) GetRPCService() common.RPCService { + return c.pxy.Get() } -func (refconfig *ReferenceConfig) getUrlMap() url.Values { +func (c *ReferenceConfig) getUrlMap() url.Values { urlMap := url.Values{} //first set user params - for k, v := range refconfig.Params { + for k, v := range c.Params { urlMap.Set(k, v) } - urlMap.Set(constant.INTERFACE_KEY, refconfig.InterfaceName) + urlMap.Set(constant.INTERFACE_KEY, c.InterfaceName) urlMap.Set(constant.TIMESTAMP_KEY, strconv.FormatInt(time.Now().Unix(), 10)) - urlMap.Set(constant.CLUSTER_KEY, refconfig.Cluster) - urlMap.Set(constant.LOADBALANCE_KEY, refconfig.Loadbalance) - urlMap.Set(constant.RETRIES_KEY, refconfig.Retries) - urlMap.Set(constant.GROUP_KEY, refconfig.Group) - urlMap.Set(constant.VERSION_KEY, refconfig.Version) - urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(refconfig.Generic)) + urlMap.Set(constant.CLUSTER_KEY, c.Cluster) + urlMap.Set(constant.LOADBALANCE_KEY, c.Loadbalance) + urlMap.Set(constant.RETRIES_KEY, c.Retries) + urlMap.Set(constant.GROUP_KEY, c.Group) + urlMap.Set(constant.VERSION_KEY, c.Version) + urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic)) urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) + if len(c.RequestTimeout) != 0 { + urlMap.Set(constant.TIMEOUT_KEY, c.RequestTimeout) + } //getty invoke async or sync - urlMap.Set(constant.ASYNC_KEY, strconv.FormatBool(refconfig.async)) + urlMap.Set(constant.ASYNC_KEY, strconv.FormatBool(c.Async)) + urlMap.Set(constant.STICKY_KEY, strconv.FormatBool(c.Sticky)) //application info urlMap.Set(constant.APPLICATION_KEY, consumerConfig.ApplicationConfig.Name) @@ -182,24 +203,29 @@ func (refconfig *ReferenceConfig) getUrlMap() url.Values { //filter var defaultReferenceFilter = constant.DEFAULT_REFERENCE_FILTERS - if refconfig.Generic { - defaultReferenceFilter = constant.GENERIC_REFERENCE_FILTERS + defaultReferenceFilter + if c.Generic { + defaultReferenceFilter = constant.GENERIC_REFERENCE_FILTERS + "," + defaultReferenceFilter } - urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, refconfig.Filter, defaultReferenceFilter)) + urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter)) - for _, v := range refconfig.Methods { + for _, v := range c.Methods { urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.Loadbalance) urlMap.Set("methods."+v.Name+"."+constant.RETRIES_KEY, v.Retries) + urlMap.Set("methods."+v.Name+"."+constant.STICKY_KEY, strconv.FormatBool(v.Sticky)) + if len(v.RequestTimeout) != 0 { + urlMap.Set("methods."+v.Name+"."+constant.TIMEOUT_KEY, v.RequestTimeout) + } } return urlMap - } -func (refconfig *ReferenceConfig) GenericLoad(id string) { - genericService := NewGenericService(refconfig.id) + +// GenericLoad ... +func (c *ReferenceConfig) GenericLoad(id string) { + genericService := NewGenericService(c.id) SetConsumerService(genericService) - refconfig.id = id - refconfig.Refer() - refconfig.Implement(genericService) + c.id = id + c.Refer(genericService) + c.Implement(genericService) return } diff --git a/config/reference_config_test.go b/config/reference_config_test.go index a81dbf06cef7d275cf6af4a7f651ff8d1600a3c9..7a65e55f09c997cb49b83f1f185faf9338cf0f5a 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -81,10 +81,12 @@ func doInitConsumer() { }, References: map[string]*ReferenceConfig{ "MockService": { + id: "MockProvider", Params: map[string]string{ "serviceid": "soa.mock", "forks": "5", }, + Sticky: false, Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", InterfaceName: "com.MockService", Protocol: "mock", @@ -103,6 +105,7 @@ func doInitConsumer() { Name: "GetUser1", Retries: "2", Loadbalance: "random", + Sticky: true, }, }, }, @@ -110,6 +113,26 @@ func doInitConsumer() { } } +var mockProvider = new(MockProvider) + +type MockProvider struct { +} + +func (m *MockProvider) Reference() string { + return "MockProvider" +} + +func (m *MockProvider) CallBack(res common.CallbackResponse) { +} + +func doInitConsumerAsync() { + doInitConsumer() + SetConsumerService(mockProvider) + for _, v := range consumerConfig.References { + v.Async = true + } +} + func doInitConsumerWithSingleRegistry() { consumerConfig = &ConsumerConfig{ ApplicationConfig: &ApplicationConfig{ @@ -161,7 +184,7 @@ func Test_ReferMultireg(t *testing.T) { extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) assert.NotNil(t, reference.invoker) assert.NotNil(t, reference.pxy) } @@ -174,13 +197,29 @@ func Test_Refer(t *testing.T) { extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) + assert.Equal(t, "soa.mock", reference.Params["serviceid"]) + assert.NotNil(t, reference.invoker) + assert.NotNil(t, reference.pxy) + } + consumerConfig = nil +} + +func Test_ReferAsync(t *testing.T) { + doInitConsumerAsync() + extension.SetProtocol("registry", GetProtocol) + extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) + + for _, reference := range consumerConfig.References { + reference.Refer(nil) assert.Equal(t, "soa.mock", reference.Params["serviceid"]) assert.NotNil(t, reference.invoker) assert.NotNil(t, reference.pxy) + assert.NotNil(t, reference.pxy.GetCallback()) } consumerConfig = nil } + func Test_ReferP2P(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) @@ -188,7 +227,7 @@ func Test_ReferP2P(t *testing.T) { m.Url = "dubbo://127.0.0.1:20000" for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) assert.NotNil(t, reference.invoker) assert.NotNil(t, reference.pxy) } @@ -202,7 +241,7 @@ func Test_ReferMultiP2P(t *testing.T) { m.Url = "dubbo://127.0.0.1:20000;dubbo://127.0.0.2:20000" for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) assert.NotNil(t, reference.invoker) assert.NotNil(t, reference.pxy) } @@ -217,7 +256,7 @@ func Test_ReferMultiP2PWithReg(t *testing.T) { m.Url = "dubbo://127.0.0.1:20000;registry://127.0.0.2:20000" for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) assert.NotNil(t, reference.invoker) assert.NotNil(t, reference.pxy) } @@ -229,7 +268,7 @@ func Test_Implement(t *testing.T) { extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) reference.Implement(&MockService{}) assert.NotNil(t, reference.GetRPCService()) @@ -245,7 +284,7 @@ func Test_Forking(t *testing.T) { m.Url = "dubbo://127.0.0.1:20000;registry://127.0.0.2:20000" for _, reference := range consumerConfig.References { - reference.Refer() + reference.Refer(nil) forks := int(reference.invoker.GetUrl().GetParamInt(constant.FORKS_KEY, constant.DEFAULT_FORKS)) assert.Equal(t, 5, forks) assert.NotNil(t, reference.pxy) @@ -254,6 +293,24 @@ func Test_Forking(t *testing.T) { consumerConfig = nil } +func Test_Sticky(t *testing.T) { + doInitConsumer() + extension.SetProtocol("dubbo", GetProtocol) + extension.SetProtocol("registry", GetProtocol) + m := consumerConfig.References["MockService"] + m.Url = "dubbo://127.0.0.1:20000;registry://127.0.0.2:20000" + + reference := consumerConfig.References["MockService"] + reference.Refer(nil) + referenceSticky := reference.invoker.GetUrl().GetParam(constant.STICKY_KEY, "false") + assert.Equal(t, "false", referenceSticky) + + method0StickKey := reference.invoker.GetUrl().GetMethodParam(reference.Methods[0].Name, constant.STICKY_KEY, "false") + assert.Equal(t, "false", method0StickKey) + method1StickKey := reference.invoker.GetUrl().GetMethodParam(reference.Methods[1].Name, constant.STICKY_KEY, "false") + assert.Equal(t, "true", method1StickKey) +} + func GetProtocol() protocol.Protocol { if regProtocol != nil { return regProtocol diff --git a/config/registry_config.go b/config/registry_config.go index 9ffa41eb5b5b3b5ae4dc9f77812c0aef5ce9835f..4e4b6e97d79a9402616b6cac954f7a09b2973dcc 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -18,7 +18,6 @@ package config import ( - "context" "net/url" "strconv" "strings" @@ -34,6 +33,7 @@ import ( "github.com/apache/dubbo-go/common/logger" ) +// RegistryConfig ... type RegistryConfig struct { Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty" property:"protocol"` //I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig @@ -46,6 +46,7 @@ type RegistryConfig struct { Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } +// UnmarshalYAML ... func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -57,6 +58,7 @@ func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } +// Prefix ... func (*RegistryConfig) Prefix() string { return constant.RegistryConfigPrefix + "|" + constant.SingleRegistryConfigPrefix } @@ -92,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), @@ -114,15 +114,16 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf return urls } -func (regconfig *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { +func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { urlMap := url.Values{} - urlMap.Set(constant.GROUP_KEY, regconfig.Group) + urlMap.Set(constant.GROUP_KEY, c.Group) urlMap.Set(constant.ROLE_KEY, strconv.Itoa(int(roleType))) - urlMap.Set(constant.REGISTRY_KEY, regconfig.Protocol) - urlMap.Set(constant.REGISTRY_TIMEOUT_KEY, regconfig.TimeoutStr) - for k, v := range regconfig.Params { + urlMap.Set(constant.REGISTRY_KEY, c.Protocol) + urlMap.Set(constant.REGISTRY_TIMEOUT_KEY, c.TimeoutStr) + for k, v := range c.Params { urlMap.Set(k, v) } + return urlMap } 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.go b/config/service.go index 2bceac4a8c20bb598dc2607c90c8206e4a448808..b7e7dc2a425b42363d570fc37a70e2e5094e7d9d 100644 --- a/config/service.go +++ b/config/service.go @@ -26,20 +26,31 @@ var ( proServices = map[string]common.RPCService{} // service name -> service ) -// SetConService is called by init() of implement of RPCService +// SetConsumerService is called by init() of implement of RPCService func SetConsumerService(service common.RPCService) { conServices[service.Reference()] = service } -// SetProService is called by init() of implement of RPCService +// SetProviderService is called by init() of implement of RPCService func SetProviderService(service common.RPCService) { proServices[service.Reference()] = service } +// GetConsumerService ... func GetConsumerService(name string) common.RPCService { return conServices[name] } +// GetProviderService ... func GetProviderService(name string) common.RPCService { return proServices[name] } + +// GetCallback ... +func GetCallback(name string) func(response common.CallbackResponse) { + service := GetConsumerService(name) + if sv, ok := service.(common.AsyncCallbackService); ok { + return sv.CallBack + } + return nil +} diff --git a/config/service_config.go b/config/service_config.go index c17846322e20120bfdf00f1afe24bd20efe7510b..7d97fa4d1e95bd79e051f77deaeafa1afcc58b0f 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -42,6 +42,7 @@ import ( "github.com/apache/dubbo-go/protocol/protocolwrapper" ) +// ServiceConfig ... type ServiceConfig struct { context context.Context id string @@ -66,6 +67,8 @@ type ServiceConfig struct { TpsLimitRejectedHandler string `yaml:"tps.limit.rejected.handler" json:"tps.limit.rejected.handler,omitempty" property:"tps.limit.rejected.handler"` ExecuteLimit string `yaml:"execute.limit" json:"execute.limit,omitempty" property:"execute.limit"` ExecuteLimitRejectedHandler string `yaml:"execute.limit.rejected.handler" json:"execute.limit.rejected.handler,omitempty" property:"execute.limit.rejected.handler"` + Auth string `yaml:"auth" json:"auth,omitempty" property:"auth"` + ParamSign string `yaml:"param.sign" json:"param.sign,omitempty" property:"param.sign"` unexported *atomic.Bool exported *atomic.Bool @@ -74,10 +77,12 @@ type ServiceConfig struct { cacheMutex sync.Mutex } +// Prefix ... func (c *ServiceConfig) Prefix() string { return constant.ServiceConfigPrefix + c.InterfaceName + "." } +// UnmarshalYAML ... func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -89,101 +94,104 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } -// The only way to get a new ServiceConfig +// 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), } - } -func (srvconfig *ServiceConfig) Export() error { +// Export ... +func (c *ServiceConfig) Export() error { // TODO: config center start here // TODO:delay export - if srvconfig.unexported != nil && srvconfig.unexported.Load() { - err := perrors.Errorf("The service %v has already unexported! ", srvconfig.InterfaceName) + if c.unexported != nil && c.unexported.Load() { + err := perrors.Errorf("The service %v has already unexported! ", c.InterfaceName) logger.Errorf(err.Error()) return err } - if srvconfig.unexported != nil && srvconfig.exported.Load() { - logger.Warnf("The service %v has already exported! ", srvconfig.InterfaceName) + if c.unexported != nil && c.exported.Load() { + logger.Warnf("The service %v has already exported! ", c.InterfaceName) return nil } - regUrls := loadRegistries(srvconfig.Registry, providerConfig.Registries, common.PROVIDER) - urlMap := srvconfig.getUrlMap() - - for _, proto := range loadProtocol(srvconfig.Protocol, providerConfig.Protocols) { + regUrls := loadRegistries(c.Registry, providerConfig.Registries, common.PROVIDER) + urlMap := c.getUrlMap() + protocolConfigs := loadProtocol(c.Protocol, providerConfig.Protocols) + if len(protocolConfigs) == 0 { + logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs ", c.InterfaceName, c.Protocol) + return nil + } + for _, proto := range protocolConfigs { // registry the service reflect - methods, err := common.ServiceMap.Register(proto.Name, srvconfig.rpcService) + methods, err := common.ServiceMap.Register(proto.Name, c.rpcService) if err != nil { - err := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", srvconfig.InterfaceName, proto.Name, err.Error()) + err := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error()) logger.Errorf(err.Error()) return err } - url := common.NewURLWithOptions(common.WithPath(srvconfig.id), + ivkURL := common.NewURLWithOptions( + common.WithPath(c.id), common.WithProtocol(proto.Name), common.WithIp(proto.Ip), common.WithPort(proto.Port), common.WithParams(urlMap), - common.WithParamsValue(constant.BEAN_NAME_KEY, srvconfig.id), + common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), common.WithMethods(strings.Split(methods, ",")), - common.WithToken(srvconfig.Token), + common.WithToken(c.Token), ) if len(regUrls) > 0 { for _, regUrl := range regUrls { - regUrl.SubURL = url + regUrl.SubURL = ivkURL - srvconfig.cacheMutex.Lock() - if srvconfig.cacheProtocol == nil { - logger.Infof(fmt.Sprintf("First load the registry protocol , url is {%v}!", url)) - srvconfig.cacheProtocol = extension.GetProtocol("registry") + c.cacheMutex.Lock() + if c.cacheProtocol == nil { + logger.Infof(fmt.Sprintf("First load the registry protocol , url is {%v}!", ivkURL)) + c.cacheProtocol = extension.GetProtocol("registry") } - srvconfig.cacheMutex.Unlock() + c.cacheMutex.Unlock() invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) - exporter := srvconfig.cacheProtocol.Export(invoker) + exporter := c.cacheProtocol.Export(invoker) if exporter == nil { - panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, url))) + panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, ivkURL))) } } } else { - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*url) + invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { - panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", url))) + panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) } } - } return nil - } -func (srvconfig *ServiceConfig) Implement(s common.RPCService) { - srvconfig.rpcService = s +// Implement ... +func (c *ServiceConfig) Implement(s common.RPCService) { + c.rpcService = s } -func (srvconfig *ServiceConfig) getUrlMap() url.Values { +func (c *ServiceConfig) getUrlMap() url.Values { urlMap := url.Values{} // first set user params - for k, v := range srvconfig.Params { + for k, v := range c.Params { urlMap.Set(k, v) } - urlMap.Set(constant.INTERFACE_KEY, srvconfig.InterfaceName) + urlMap.Set(constant.INTERFACE_KEY, c.InterfaceName) urlMap.Set(constant.TIMESTAMP_KEY, strconv.FormatInt(time.Now().Unix(), 10)) - urlMap.Set(constant.CLUSTER_KEY, srvconfig.Cluster) - urlMap.Set(constant.LOADBALANCE_KEY, srvconfig.Loadbalance) - urlMap.Set(constant.WARMUP_KEY, srvconfig.Warmup) - urlMap.Set(constant.RETRIES_KEY, srvconfig.Retries) - urlMap.Set(constant.GROUP_KEY, srvconfig.Group) - urlMap.Set(constant.VERSION_KEY, srvconfig.Version) + urlMap.Set(constant.CLUSTER_KEY, c.Cluster) + urlMap.Set(constant.LOADBALANCE_KEY, c.Loadbalance) + urlMap.Set(constant.WARMUP_KEY, c.Warmup) + urlMap.Set(constant.RETRIES_KEY, c.Retries) + urlMap.Set(constant.GROUP_KEY, c.Group) + urlMap.Set(constant.VERSION_KEY, c.Version) urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) // application info urlMap.Set(constant.APPLICATION_KEY, providerConfig.ApplicationConfig.Name) @@ -195,22 +203,26 @@ func (srvconfig *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.ENVIRONMENT_KEY, providerConfig.ApplicationConfig.Environment) // filter - urlMap.Set(constant.SERVICE_FILTER_KEY, mergeValue(providerConfig.Filter, srvconfig.Filter, constant.DEFAULT_SERVICE_FILTERS)) + urlMap.Set(constant.SERVICE_FILTER_KEY, mergeValue(providerConfig.Filter, c.Filter, constant.DEFAULT_SERVICE_FILTERS)) // filter special config - urlMap.Set(constant.ACCESS_LOG_KEY, srvconfig.AccessLog) + urlMap.Set(constant.ACCESS_LOG_KEY, c.AccessLog) // tps limiter - urlMap.Set(constant.TPS_LIMIT_STRATEGY_KEY, srvconfig.TpsLimitStrategy) - urlMap.Set(constant.TPS_LIMIT_INTERVAL_KEY, srvconfig.TpsLimitInterval) - urlMap.Set(constant.TPS_LIMIT_RATE_KEY, srvconfig.TpsLimitRate) - urlMap.Set(constant.TPS_LIMITER_KEY, srvconfig.TpsLimiter) - urlMap.Set(constant.TPS_REJECTED_EXECUTION_HANDLER_KEY, srvconfig.TpsLimitRejectedHandler) + urlMap.Set(constant.TPS_LIMIT_STRATEGY_KEY, c.TpsLimitStrategy) + urlMap.Set(constant.TPS_LIMIT_INTERVAL_KEY, c.TpsLimitInterval) + urlMap.Set(constant.TPS_LIMIT_RATE_KEY, c.TpsLimitRate) + urlMap.Set(constant.TPS_LIMITER_KEY, c.TpsLimiter) + urlMap.Set(constant.TPS_REJECTED_EXECUTION_HANDLER_KEY, c.TpsLimitRejectedHandler) // execute limit filter - urlMap.Set(constant.EXECUTE_LIMIT_KEY, srvconfig.ExecuteLimit) - urlMap.Set(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, srvconfig.ExecuteLimitRejectedHandler) + urlMap.Set(constant.EXECUTE_LIMIT_KEY, c.ExecuteLimit) + urlMap.Set(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, c.ExecuteLimitRejectedHandler) - for _, v := range srvconfig.Methods { + // auth filter + urlMap.Set(constant.SERVICE_AUTH_KEY, c.Auth) + urlMap.Set(constant.PARAMTER_SIGNATURE_ENABLE_KEY, c.ParamSign) + + for _, v := range c.Methods { prefix := "methods." + v.Name + "." urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.Loadbalance) urlMap.Set(prefix+constant.RETRIES_KEY, v.Retries) @@ -226,5 +238,4 @@ func (srvconfig *ServiceConfig) getUrlMap() url.Values { } return urlMap - } diff --git a/config/service_config_test.go b/config/service_config_test.go index 8ae67533bd1cdd1f9170efd762de51d371d0ad38..6f3230890348e77ea26c9c0eaf9165090c8cd09f 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -93,6 +93,30 @@ func doInitProvider() { }, }, }, + "MockServiceNoRightProtocol": { + InterfaceName: "com.MockService", + Protocol: "mock1", + Registry: "shanghai_reg1,shanghai_reg2,hangzhou_reg1,hangzhou_reg2", + Cluster: "failover", + Loadbalance: "random", + Retries: "3", + Group: "huadong_idc", + Version: "1.0.0", + Methods: []*MethodConfig{ + { + Name: "GetUser", + Retries: "2", + Loadbalance: "random", + Weight: 200, + }, + { + Name: "GetUser1", + Retries: "2", + Loadbalance: "random", + Weight: 200, + }, + }, + }, }, Protocols: map[string]*ProtocolConfig{ "mock": { diff --git a/config/testdata/consumer_config.properties b/config/testdata/consumer_config.properties new file mode 100644 index 0000000000000000000000000000000000000000..da9fe4f3b3f2ae47dafc8252e388678e7cd5d03b --- /dev/null +++ b/config/testdata/consumer_config.properties @@ -0,0 +1,52 @@ +filter= +request_timeout=100ms +connect_timeout=100ms +check=true +application.organization=ikurento.com +application.name=BDTService +application.module=dubbogo user-info client +application.version=0.0.1 +application.owner=ZX +application.environment=dev +registries.hangzhouzk.protocol=zookeeper +registries.hangzhouzk.timeout=3s +registries.hangzhouzk.address=127.0.0.1:2181 +registries.hangzhouzk.username= +registries.hangzhouzk.password= +registries.shanghaizk.protocol=zookeeper +registries.shanghaizk.timeout=3s +registries.shanghaizk.address=127.0.0.1:2182 +registries.shanghaizk.username= +registries.shanghaizk.password= +references.UserProvider.registry=hangzhouzk,shanghaizk +references.UserProvider.filter= +references.UserProvider.version=1.0 +references.UserProvider.group=as +references.UserProvider.interface=com.ikurento.user.UserProvider +references.UserProvider.url=dubbo://127.0.0.1:20000/UserProvider +references.UserProvider.cluster=failover +references.UserProvider.methods[0].name=GetUser +references.UserProvider.methods[0].retries=3 +references.UserProvider.params.serviceid=soa.com.ikurento.user.UserProvider +references.UserProvider.params.forks=5 +protocol_conf.dubbo.reconnect_interval=0 +protocol_conf.dubbo.connection_number=2 +protocol_conf.dubbo.heartbeat_period=5s +protocol_conf.dubbo.session_timeout=20s +protocol_conf.dubbo.pool_size=64 +protocol_conf.dubbo.pool_ttl=600 +protocol_conf.dubbo.gr_pool_size=1200 +protocol_conf.dubbo.queue_len=64 +protocol_conf.dubbo.queue_number=60 +protocol_conf.dubbo.getty_session_param.compress_encoding=false +protocol_conf.dubbo.getty_session_param.tcp_no_delay=true +protocol_conf.dubbo.getty_session_param.tcp_keep_alive=true +protocol_conf.dubbo.getty_session_param.keep_alive_period=120s +protocol_conf.dubbo.getty_session_param.tcp_r_buf_size=262144 +protocol_conf.dubbo.getty_session_param.tcp_w_buf_size=65536 +protocol_conf.dubbo.getty_session_param.pkg_wq_size=512 +protocol_conf.dubbo.getty_session_param.tcp_read_timeout=1s +protocol_conf.dubbo.getty_session_param.tcp_write_timeout=5s +protocol_conf.dubbo.getty_session_param.wait_timeout=1s +protocol_conf.dubbo.getty_session_param.max_msg_len=1024 +protocol_conf.dubbo.getty_session_param.session_name=client \ No newline at end of file diff --git a/config/testdata/consumer_config.yml b/config/testdata/consumer_config.yml index 9fd50bb4d35a40d8532c9a644a86ad6834f8e89b..2034186c0fa0ccf21c3f6fb9df0f5cfd69315113 100644 --- a/config/testdata/consumer_config.yml +++ b/config/testdata/consumer_config.yml @@ -41,21 +41,36 @@ references: interface : "com.ikurento.user.UserProvider" url: "dubbo://127.0.0.1:20000/UserProvider" cluster: "failover" + timeout: "3s" methods : - name: "GetUser" retries: "3" + timeout: "5s" params: "serviceid": "soa.com.ikurento.user.UserProvider" "forks": 5 +shutdown_conf: + timeout: 60s + step_timeout: 10s + protocol_conf: + # when you choose the Dubbo protocol, the following configuration takes effect dubbo: reconnect_interval: 0 + # reconnect_interval is the actual number of connections a session can use connection_number: 2 - heartbeat_period: "5s" - session_timeout: "20s" - pool_size: 64 + # heartbeat_period is heartbeat interval between server and client connection. + # Effective by client configuration + heartbeat_period: "30s" + # when the session is inactive for more than session_timeout, the session may be closed + session_timeout: "30s" + # a reference has the size of the session connection pool + # that is the maximum number of sessions it may have + pool_size: 4 + # dubbo-go uses getty as the network connection library. + # The following is the relevant configuration of getty pool_ttl: 600 # gr_pool_size is recommended to be set to [cpu core number] * 100 gr_pool_size: 1200 @@ -63,6 +78,8 @@ protocol_conf: queue_len: 64 # queue_number is recommended to be set to gr_pool_size / 20 queue_number: 60 + # dubbo-go uses getty as the network connection library. + # The following is the relevant configuration of getty getty_session_param: compress_encoding: false tcp_no_delay: true @@ -74,5 +91,7 @@ protocol_conf: tcp_read_timeout: "1s" tcp_write_timeout: "5s" wait_timeout: "1s" - max_msg_len: 1024 + # maximum len of data per request + # this refers to the total amount of data requested or returned + max_msg_len: 102400 session_name: "client" diff --git a/config/testdata/consumer_config_with_configcenter.yml b/config/testdata/consumer_config_with_configcenter.yml index 0550cc89741b6a490aaba9ff8906d7dda1b3ed49..ebe56fa93f9f5728aa365ee5b7a99b6bb5857a8e 100644 --- a/config/testdata/consumer_config_with_configcenter.yml +++ b/config/testdata/consumer_config_with_configcenter.yml @@ -17,6 +17,10 @@ references: - name: "GetUser" retries: "3" +shutdown_conf: + timeout: 60s + step_timeout: 10s + protocol_conf: dubbo: reconnect_interval: 0 diff --git a/config/testdata/consumer_config_with_configcenter_apollo.yml b/config/testdata/consumer_config_with_configcenter_apollo.yml new file mode 100644 index 0000000000000000000000000000000000000000..49b8fff59541766729433138574d909ae4b9566e --- /dev/null +++ b/config/testdata/consumer_config_with_configcenter_apollo.yml @@ -0,0 +1,24 @@ +# use apollo config center for fetch config file +# default config file namespace is dubbo.properties +# consumer config file Ref:consumer_config.properties +# provider config file Ref:provider_config.properties +config_center: + protocol: apollo + address: 106.12.25.204:8080 + group: testApplication_yang + cluster: dev + # 'namespace' can be used for router rule , default value is dubbo.properties + # but if you want to change router rule config file ,just open this item +# namespace: governance.properties + # 'config_file' is not necessary ,default : dubbo.properties + # but if you want to change config file ,just open this item +# config_file: mockDubbog.properties + +# application config required +application: + organization: "ikurento.com" + name: "BDTService" + module: "dubbogo user-info server" + version: "0.0.1" + owner: "ZX" + environment: "dev" \ No newline at end of file diff --git a/config/testdata/consumer_config_withoutProtocol.yml b/config/testdata/consumer_config_withoutProtocol.yml index 5e57c7ddf6e82152e4f207b2d06df1443766717c..32bad8b91db3fac9c026fca36c5dc3b84f4c3fc9 100644 --- a/config/testdata/consumer_config_withoutProtocol.yml +++ b/config/testdata/consumer_config_withoutProtocol.yml @@ -48,6 +48,10 @@ references: "soa.com.ikurento.user.UserProvider" "forks": 5 +shutdown_conf: + timeout: 60s + step_timeout: 10s + protocol_conf: dubbo: reconnect_interval: 0 diff --git a/config/testdata/provider_config.properties b/config/testdata/provider_config.properties new file mode 100644 index 0000000000000000000000000000000000000000..f7d70f5cd635cae0b14d79890c1bab976978d1c6 --- /dev/null +++ b/config/testdata/provider_config.properties @@ -0,0 +1,58 @@ +filter= +application.organization=ikurento.com +application.name=BDTService +application.module=dubbogo user-info server +application.version=0.0.1 +application.owner=ZX +application.environment=dev +registries.hangzhouzk.protocol=zookeeper +registries.hangzhouzk.timeout=3s +registries.hangzhouzk.address=127.0.0.1:2181 +registries.hangzhouzk.username= +registries.hangzhouzk.password= +registries.shanghaizk.protocol=zookeeper +registries.shanghaizk.timeout=3s +registries.shanghaizk.address=127.0.0.1:2182 +registries.shanghaizk.username= +registries.shanghaizk.password= +services.UserProvider.registry=hangzhouzk,shanghaizk +services.UserProvider.filter= +services.UserProvider.tps.limiter=default +services.UserProvider.tps.limit.interval=60000 +services.UserProvider.tps.limit.rate=200 +services.UserProvider.tps.limit.strategy=slidingWindow +services.UserProvider.tps.limit.rejected.handler=default +services.UserProvider.execute.limit=200 +services.UserProvider.execute.limit.rejected.handler=default +services.UserProvider.protocol=dubbo +services.UserProvider.interface=com.ikurento.user.UserProvider +services.UserProvider.loadbalance=random +services.UserProvider.version=1.0 +services.UserProvider.group=as +services.UserProvider.warmup=100 +services.UserProvider.cluster=failover +services.UserProvider.methods[0].name=GetUser +services.UserProvider.methods[0].retries=1 +services.UserProvider.methods[0].loadbalance=random +services.UserProvider.methods[0].execute.limit=200 +services.UserProvider.methods[0].execute.limit.rejected.handler=default +protocols.dubbo.name=dubbo +protocols.dubbo.ip=127.0.0.1 +protocols.dubbo.port=20000 +protocol_conf.dubbo.session_number=700 +protocol_conf.dubbo.session_timeout=20s +protocol_conf.dubbo.gr_pool_size=120 +protocol_conf.dubbo.queue_len=64 +protocol_conf.dubbo.queue_number=6 +protocol_conf.dubbo.getty_session_param.compress_encoding=false +protocol_conf.dubbo.getty_session_param.tcp_no_delay=true +protocol_conf.dubbo.getty_session_param.tcp_keep_alive=true +protocol_conf.dubbo.getty_session_param.keep_alive_period=120s +protocol_conf.dubbo.getty_session_param.tcp_r_buf_size=262144 +protocol_conf.dubbo.getty_session_param.tcp_w_buf_size=65536 +protocol_conf.dubbo.getty_session_param.pkg_wq_size=512 +protocol_conf.dubbo.getty_session_param.tcp_read_timeout=1s +protocol_conf.dubbo.getty_session_param.tcp_write_timeout=5s +protocol_conf.dubbo.getty_session_param.wait_timeout=1s +protocol_conf.dubbo.getty_session_param.max_msg_len=1024 +protocol_conf.dubbo.getty_session_param.session_name=server \ No newline at end of file diff --git a/config/testdata/provider_config.yml b/config/testdata/provider_config.yml index 080feb7dcd1cccd06ae436b2854b2531177d23e3..7c46f9101aa9a6ecb88a92953dfcec28dda4e0ff 100644 --- a/config/testdata/provider_config.yml +++ b/config/testdata/provider_config.yml @@ -71,6 +71,10 @@ protocols: # ip: "127.0.0.1" # port: 20001 +shutdown_conf: + timeout: 60s + step_timeout: 10s + protocol_conf: dubbo: session_number: 700 diff --git a/config/testdata/provider_config_withoutProtocol.yml b/config/testdata/provider_config_withoutProtocol.yml index 2f65868d4948db9a8b99c500014ea1307569d86f..532d3005aa351820bd540b31e2721dc2a0b5c6ed 100644 --- a/config/testdata/provider_config_withoutProtocol.yml +++ b/config/testdata/provider_config_withoutProtocol.yml @@ -51,6 +51,10 @@ protocols: # ip: "127.0.0.1" # port: 20001 +shutdown_conf: + timeout: 60s + step_timeout: 10s + protocol_conf: dubbo: session_number: 700 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/factory.go b/config_center/apollo/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..a5a69e121598bea4194398423775a99f04b61ced --- /dev/null +++ b/config_center/apollo/factory.go @@ -0,0 +1,45 @@ +/* +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 apollo + +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("apollo", createDynamicConfigurationFactory) +} + +func createDynamicConfigurationFactory() config_center.DynamicConfigurationFactory { + return &apolloConfigurationFactory{} +} + +type apolloConfigurationFactory struct{} + +func (f *apolloConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { + dynamicConfiguration, err := newApolloConfiguration(url) + if err != nil { + return nil, err + } + dynamicConfiguration.SetParser(&parser.DefaultConfigurationParser{}) + return dynamicConfiguration, err + +} diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go new file mode 100644 index 0000000000000000000000000000000000000000..4dc19817846fe5c9c0552738f2058a15d20efabc --- /dev/null +++ b/config_center/apollo/impl.go @@ -0,0 +1,169 @@ +/* +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 apollo + +import ( + "fmt" + "regexp" + "strings" + "sync" +) + +import ( + "github.com/pkg/errors" + "github.com/zouyx/agollo" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + cc "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/parser" + "github.com/apache/dubbo-go/remoting" +) + +const ( + apolloProtocolPrefix = "http://" + apolloConfigFormat = "%s.%s" +) + +type apolloConfiguration struct { + url *common.URL + + listeners sync.Map + appConf *agollo.AppConfig + parser parser.ConfigurationParser +} + +func newApolloConfiguration(url *common.URL) (*apolloConfiguration, error) { + c := &apolloConfiguration{ + url: url, + } + configAddr := c.getAddressWithProtocolPrefix(url) + configCluster := url.GetParam(constant.CONFIG_CLUSTER_KEY, "") + + appId := url.GetParam(constant.CONFIG_APP_ID_KEY, "") + namespaces := getProperties(url.GetParam(constant.CONFIG_NAMESPACE_KEY, cc.DEFAULT_GROUP)) + c.appConf = &agollo.AppConfig{ + AppId: appId, + Cluster: configCluster, + NamespaceName: namespaces, + Ip: configAddr, + } + + agollo.InitCustomConfig(func() (*agollo.AppConfig, error) { + return c.appConf, nil + }) + + return c, agollo.Start() +} + +func getChangeType(change agollo.ConfigChangeType) remoting.EventType { + switch change { + case agollo.ADDED: + return remoting.EventTypeAdd + case agollo.DELETED: + return remoting.EventTypeDel + default: + return remoting.EventTypeUpdate + } +} + +func (c *apolloConfiguration) AddListener(key string, listener cc.ConfigurationListener, opts ...cc.Option) { + k := &cc.Options{} + for _, opt := range opts { + opt(k) + } + + key = k.Group + key + l, _ := c.listeners.LoadOrStore(key, NewApolloListener()) + l.(*apolloListener).AddListener(listener) +} + +func (c *apolloConfiguration) RemoveListener(key string, listener cc.ConfigurationListener, opts ...cc.Option) { + k := &cc.Options{} + for _, opt := range opts { + opt(k) + } + + key = k.Group + key + l, ok := c.listeners.Load(key) + if ok { + l.(*apolloListener).RemoveListener(listener) + } +} + +func getProperties(namespace string) string { + return getNamespaceName(namespace, agollo.Properties) +} + +func getNamespaceName(namespace string, configFileFormat agollo.ConfigFileFormat) string { + return fmt.Sprintf(apolloConfigFormat, namespace, configFileFormat) +} + +func (c *apolloConfiguration) GetInternalProperty(key string, opts ...cc.Option) (string, error) { + config := agollo.GetConfig(c.appConf.NamespaceName) + if config == nil { + return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key)) + } + return config.GetStringValue(key, ""), nil +} + +func (c *apolloConfiguration) GetRule(key string, opts ...cc.Option) (string, error) { + return c.GetInternalProperty(key, opts...) +} + +func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (string, error) { + /** + * when group is not null, we are getting startup configs(config file) from Config Center, for example: + * key=dubbo.propertie + */ + config := agollo.GetConfig(key) + if config == nil { + return "", errors.New(fmt.Sprintf("nothing in namespace:%s ", key)) + } + return config.GetContent(agollo.Properties), nil +} + +func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) string { + address := url.Location + converted := address + if len(address) != 0 { + reg := regexp.MustCompile("\\s+") + address = reg.ReplaceAllString(address, "") + parts := strings.Split(address, ",") + addrs := make([]string, 0) + for _, part := range parts { + addr := part + if !strings.HasPrefix(part, apolloProtocolPrefix) { + addr = apolloProtocolPrefix + part + } + addrs = append(addrs, addr) + } + converted = strings.Join(addrs, ",") + } + return converted +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..a95524b41b887313993aad4e774ed6d96b24c08f --- /dev/null +++ b/config_center/apollo/impl_test.go @@ -0,0 +1,274 @@ +/* + * 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 apollo + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "sync" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/parser" + "github.com/apache/dubbo-go/remoting" +) + +const ( + mockAppId = "testApplication_yang" + mockCluster = "dev" + mockNamespace = "mockDubbog.properties" + mockNotifyRes = `[{ + "namespaceName": "mockDubbog.properties", + "notificationId": 53050, + "messages": { + "details": { + "testApplication_yang+default+mockDubbog": 53050 + } + } +}]` + mockServiceConfigRes = `[{ + "appName": "APOLLO-CONFIGSERVICE", + "instanceId": "instance-300408ep:apollo-configservice:8080", + "homepageUrl": "http://localhost:8080" +}]` +) + +var ( + mockConfigRes = `{ + "appId": "testApplication_yang", + "cluster": "default", + "namespaceName": "mockDubbog.properties", + "configurations": { + "registries.hangzhouzk.username": "", + "application.owner": "ZX", + "registries.shanghaizk.username": "", + "protocols.dubbo.ip": "127.0.0.1", + "protocol_conf.dubbo.getty_session_param.tcp_write_timeout": "5s", + "services.UserProvider.cluster": "failover", + "application.module": "dubbogo user-info server", + "services.UserProvider.interface": "com.ikurento.user.UserProvider", + "protocol_conf.dubbo.getty_session_param.compress_encoding": "false", + "registries.shanghaizk.address": "127.0.0.1:2182", + "protocol_conf.dubbo.session_timeout": "20s", + "registries.shanghaizk.timeout": "3s", + "protocol_conf.dubbo.getty_session_param.keep_alive_period": "120s", + "services.UserProvider.warmup": "100", + "application.version": "0.0.1", + "registries.hangzhouzk.protocol": "zookeeper", + "registries.hangzhouzk.password": "", + "protocols.dubbo.name": "dubbo", + "protocol_conf.dubbo.getty_session_param.wait_timeout": "1s", + "protocols.dubbo.port": "20000", + "application_config.owner": "demo", + "application_config.name": "demo", + "application_config.version": "0.0.1", + "application_config.environment": "dev", + "protocol_conf.dubbo.getty_session_param.session_name": "server", + "application.name": "BDTService", + "registries.hangzhouzk.timeout": "3s", + "protocol_conf.dubbo.getty_session_param.tcp_read_timeout": "1s", + "services.UserProvider.loadbalance": "random", + "protocol_conf.dubbo.session_number": "700", + "protocol_conf.dubbo.getty_session_param.max_msg_len": "1024", + "services.UserProvider.registry": "hangzhouzk", + "application_config.module": "demo", + "services.UserProvider.methods[0].name": "GetUser", + "protocol_conf.dubbo.getty_session_param.tcp_no_delay": "true", + "services.UserProvider.methods[0].retries": "1", + "protocol_conf.dubbo.getty_session_param.tcp_w_buf_size": "65536", + "protocol_conf.dubbo.getty_session_param.tcp_r_buf_size": "262144", + "registries.shanghaizk.password": "", + "application_config.organization": "demo", + "registries.shanghaizk.protocol": "zookeeper", + "protocol_conf.dubbo.getty_session_param.tcp_keep_alive": "true", + "registries.hangzhouzk.address": "127.0.0.1:2181", + "application.environment": "dev", + "services.UserProvider.protocol": "dubbo", + "application.organization": "ikurento.com", + "protocol_conf.dubbo.getty_session_param.pkg_wq_size": "512", + "services.UserProvider.methods[0].loadbalance": "random" + }, + "releaseKey": "20191104105242-0f13805d89f834a4" +}` +) + +func initApollo() *httptest.Server { + handlerMap := make(map[string]func(http.ResponseWriter, *http.Request), 1) + handlerMap[mockNamespace] = configResponse + + return runMockConfigServer(handlerMap, notifyResponse) +} + +func configResponse(rw http.ResponseWriter, req *http.Request) { + result := fmt.Sprintf(mockConfigRes) + fmt.Fprintf(rw, "%s", result) +} + +func notifyResponse(rw http.ResponseWriter, req *http.Request) { + result := fmt.Sprintf(mockNotifyRes) + fmt.Fprintf(rw, "%s", result) +} + +func serviceConfigResponse(rw http.ResponseWriter, req *http.Request) { + result := fmt.Sprintf(mockServiceConfigRes) + fmt.Fprintf(rw, "%s", result) +} + +// 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) + for namespace, handler := range handlerMap { + uri := fmt.Sprintf("/configs/%s/%s/%s", mockAppId, mockCluster, namespace) + uriHandlerMap[uri] = handler + } + uriHandlerMap["/notifications/v2"] = notifyHandler + uriHandlerMap["/services/config"] = serviceConfigResponse + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + uri := r.RequestURI + for path, handler := range uriHandlerMap { + if strings.HasPrefix(uri, path) { + handler(w, r) + break + } + } + })) + + return ts +} + +func Test_GetConfig(t *testing.T) { + configuration := initMockApollo(t) + configs, err := configuration.GetProperties(mockNamespace, config_center.WithGroup("dubbo")) + assert.NoError(t, err) + configuration.SetParser(&parser.DefaultConfigurationParser{}) + mapContent, err := configuration.Parser().Parse(configs) + assert.NoError(t, err) + assert.Equal(t, "ikurento.com", mapContent["application.organization"]) + deleteMockJson(t) +} + +func Test_GetConfigItem(t *testing.T) { + configuration := initMockApollo(t) + configs, err := configuration.GetInternalProperty("application.organization") + assert.NoError(t, err) + configuration.SetParser(&parser.DefaultConfigurationParser{}) + assert.NoError(t, err) + assert.Equal(t, "ikurento.com", configs) + deleteMockJson(t) +} + +func initMockApollo(t *testing.T) *apolloConfiguration { + c := &config.BaseConfig{ConfigCenterConfig: &config.ConfigCenterConfig{ + Protocol: "apollo", + Address: "106.12.25.204:8080", + AppId: "testApplication_yang", + Cluster: "dev", + Namespace: "mockDubbog", + }} + apollo := initApollo() + apolloUrl := strings.ReplaceAll(apollo.URL, "http", "apollo") + url, err := common.NewURL(apolloUrl, common.WithParams(c.ConfigCenterConfig.GetUrlMap())) + assert.NoError(t, err) + configuration, err := newApolloConfiguration(&url) + assert.NoError(t, err) + return configuration +} + +func TestAddListener(t *testing.T) { + listener := &apolloDataListener{} + listener.wg.Add(1) + apollo := initMockApollo(t) + mockConfigRes = `{ + "appId": "testApplication_yang", + "cluster": "default", + "namespaceName": "mockDubbog.properties", + "configurations": { + "registries.hangzhouzk.username": "11111" + }, + "releaseKey": "20191104105242-0f13805d89f834a4" +}` + apollo.AddListener(mockNamespace, listener) + listener.wg.Wait() + assert.Equal(t, "registries.hangzhouzk.username", listener.event) + assert.Greater(t, listener.count, 0) + deleteMockJson(t) +} + +func TestRemoveListener(t *testing.T) { + listener := &apolloDataListener{} + apollo := initMockApollo(t) + mockConfigRes = `{ + "appId": "testApplication_yang", + "cluster": "default", + "namespaceName": "mockDubbog.properties", + "configurations": { + "registries.hangzhouzk.username": "11111" + }, + "releaseKey": "20191104105242-0f13805d89f834a4" +}` + apollo.AddListener(mockNamespace, listener) + apollo.RemoveListener(mockNamespace, listener) + assert.Equal(t, "", listener.event) + listenerCount := 0 + apollo.listeners.Range(func(key, value interface{}) bool { + apolloListener := value.(*apolloListener) + for e := range apolloListener.listeners { + fmt.Println(e) + listenerCount++ + } + return true + }) + assert.Equal(t, listenerCount, 0) + assert.Equal(t, listener.count, 0) + deleteMockJson(t) +} + +type apolloDataListener struct { + wg sync.WaitGroup + count int + event string +} + +func (l *apolloDataListener) Process(configType *config_center.ConfigChangeEvent) { + if configType.ConfigType != remoting.EventTypeUpdate { + return + } + l.wg.Done() + l.count++ + l.event = configType.Key +} + +func deleteMockJson(t *testing.T) { + // 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/apollo/listener.go b/config_center/apollo/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..820d02fb48e2204c3f1eb74fd5624132a63d367e --- /dev/null +++ b/config_center/apollo/listener.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 apollo + +import ( + "github.com/zouyx/agollo" +) + +import ( + "github.com/apache/dubbo-go/config_center" +) + +type apolloListener struct { + listeners map[config_center.ConfigurationListener]struct{} +} + +// NewApolloListener ... +func NewApolloListener() *apolloListener { + return &apolloListener{ + listeners: make(map[config_center.ConfigurationListener]struct{}, 0), + } +} + +// OnChange ... +func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) { + for key, change := range changeEvent.Changes { + for listener := range a.listeners { + listener.Process(&config_center.ConfigChangeEvent{ + ConfigType: getChangeType(change.ChangeType), + Key: key, + Value: change.NewValue, + }) + } + } +} + +// AddListener ... +func (a *apolloListener) AddListener(l config_center.ConfigurationListener) { + if _, ok := a.listeners[l]; !ok { + a.listeners[l] = struct{}{} + agollo.AddChangeListener(a) + } +} + +// RemoveListener ... +func (a *apolloListener) RemoveListener(l config_center.ConfigurationListener) { + delete(a.listeners, l) +} diff --git a/config_center/configuration_listener.go b/config_center/configuration_listener.go index 1419bcdd0ce10ec15d0c24c2439bb02747ce5391..e70e4f68075c51c33f1110ef44a7b703e36fb78d 100644 --- a/config_center/configuration_listener.go +++ b/config_center/configuration_listener.go @@ -25,10 +25,12 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// ConfigurationListener ... type ConfigurationListener interface { Process(*ConfigChangeEvent) } +// ConfigChangeEvent ... type ConfigChangeEvent struct { Key string Value interface{} diff --git a/config_center/configurator.go b/config_center/configurator.go index 3ba293ec60302b76becce357f49b2baa543f69cd..ffa9034e05c4c3d4cc254886e2ed19576f155dec 100644 --- a/config_center/configurator.go +++ b/config_center/configurator.go @@ -21,6 +21,7 @@ import ( "github.com/apache/dubbo-go/common" ) +// Configurator ... type Configurator interface { GetUrl() *common.URL Configure(url *common.URL) diff --git a/config_center/configurator/mock.go b/config_center/configurator/mock.go index 1f03d107c8f588cfd4c23c9086bb0fbe42e05fff..d294b9195db9cfe60056bc29ec26816f740ea396 100644 --- a/config_center/configurator/mock.go +++ b/config_center/configurator/mock.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package configurator import ( @@ -22,6 +23,7 @@ import ( "github.com/apache/dubbo-go/config_center" ) +// NewMockConfigurator ... func NewMockConfigurator(url *common.URL) config_center.Configurator { return &mockConfigurator{configuratorUrl: url} } @@ -30,10 +32,12 @@ type mockConfigurator struct { configuratorUrl *common.URL } +// GetUrl ... func (c *mockConfigurator) GetUrl() *common.URL { return c.configuratorUrl } +// Configure ... func (c *mockConfigurator) Configure(url *common.URL) { if cluster := c.GetUrl().GetParam(constant.CLUSTER_KEY, ""); cluster != "" { url.SetParam(constant.CLUSTER_KEY, cluster) diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index e85b4d3ec9d5e6f9f7163cefce3f328f8dcc225a..18415bee3a28b37ffc2f3f73cc7309b685de5408 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package configurator import ( @@ -21,7 +22,7 @@ import ( ) import ( - "github.com/dubbogo/gost/container/gxset" + gxset "github.com/dubbogo/gost/container/set" gxnet "github.com/dubbogo/gost/net" ) @@ -35,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 a585f4217f81a5d600ec9a48c12b3b47ff2d5322..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,45 +31,49 @@ 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, "")) } 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 1028b26d963cfcb02636113abc3e482bb22192a0..d6c3b06b327f16c709b09121e589db6694d3663e 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -22,39 +22,60 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/config_center/parser" ) ////////////////////////////////////////// // DynamicConfiguration ////////////////////////////////////////// -const DEFAULT_GROUP = "dubbo" -const DEFAULT_CONFIG_TIMEOUT = "10s" +const ( + // DEFAULT_GROUP: default group + DEFAULT_GROUP = "dubbo" + // DEFAULT_CONFIG_TIMEOUT: default config timeout + DEFAULT_CONFIG_TIMEOUT = "10s" +) +// DynamicConfiguration ... type DynamicConfiguration interface { Parser() parser.ConfigurationParser SetParser(parser.ConfigurationParser) AddListener(string, ConfigurationListener, ...Option) RemoveListener(string, ConfigurationListener, ...Option) - GetConfig(string, ...Option) (string, error) - GetConfigs(string, ...Option) (string, error) + //GetProperties get properties file + GetProperties(string, ...Option) (string, error) + + //GetRule get Router rule properties file + GetRule(string, ...Option) (string, error) + + //GetInternalProperty get value by key in Default properties file(dubbo.properties) + GetInternalProperty(string, ...Option) (string, error) } +// Options ... type Options struct { Group string Timeout time.Duration } +// Option ... type Option func(*Options) +// WithGroup ... func WithGroup(group string) Option { return func(opt *Options) { opt.Group = group } } +// WithTimeout ... func WithTimeout(time time.Duration) Option { return func(opt *Options) { opt.Timeout = time } } + +//GetRuleKey The format is '{interfaceName}:[version]:[group]' +func GetRuleKey(url common.URL) string { + return url.ColonSeparatedKey() +} diff --git a/config_center/dynamic_configuration_factory.go b/config_center/dynamic_configuration_factory.go index 0720896fb615f8639c20a46d2078c3dfcd112c32..9f9b13227f6623a02b0261c46d8d1e43624005f8 100644 --- a/config_center/dynamic_configuration_factory.go +++ b/config_center/dynamic_configuration_factory.go @@ -21,6 +21,7 @@ import ( "github.com/apache/dubbo-go/common" ) +// DynamicConfigurationFactory ... type DynamicConfigurationFactory interface { GetDynamicConfiguration(*common.URL) (DynamicConfiguration, error) } diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go index 47b509231d225491e6791e295a707756256f61d5..4d972b629abb7abd7cc0d0018026e4ccc04a1e4f 100644 --- a/config_center/mock_dynamic_config.go +++ b/config_center/mock_dynamic_config.go @@ -32,6 +32,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// MockDynamicConfigurationFactory ... type MockDynamicConfigurationFactory struct { Content string } @@ -41,7 +42,8 @@ var ( dynamicConfiguration *MockDynamicConfiguration ) -func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(url *common.URL) (DynamicConfiguration, error) { +// GetDynamicConfiguration ... +func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(_ *common.URL) (DynamicConfiguration, error) { var err error once.Do(func() { dynamicConfiguration = &MockDynamicConfiguration{listener: map[string]ConfigurationListener{}} @@ -79,36 +81,59 @@ func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(url *common.UR } +// MockDynamicConfiguration ... type MockDynamicConfiguration struct { parser parser.ConfigurationParser content string listener map[string]ConfigurationListener } -func (c *MockDynamicConfiguration) AddListener(key string, listener ConfigurationListener, opions ...Option) { +// AddListener ... +func (c *MockDynamicConfiguration) AddListener(key string, listener ConfigurationListener, _ ...Option) { c.listener[key] = listener } -func (c *MockDynamicConfiguration) RemoveListener(key string, listener ConfigurationListener, opions ...Option) { +// RemoveListener ... +func (c *MockDynamicConfiguration) RemoveListener(_ string, _ ConfigurationListener, _ ...Option) { } -func (c *MockDynamicConfiguration) GetConfig(key string, opts ...Option) (string, error) { +// GetConfig ... +func (c *MockDynamicConfiguration) GetConfig(_ string, _ ...Option) (string, error) { return c.content, nil } -//For zookeeper, getConfig and getConfigs have the same meaning. +// GetConfigs For zookeeper, getConfig and getConfigs have the same meaning. func (c *MockDynamicConfiguration) GetConfigs(key string, opts ...Option) (string, error) { return c.GetConfig(key, opts...) } +// Parser ... func (c *MockDynamicConfiguration) Parser() parser.ConfigurationParser { return c.parser } + +// SetParser ... func (c *MockDynamicConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } +// GetProperties ... +func (c *MockDynamicConfiguration) GetProperties(_ string, _ ...Option) (string, error) { + return c.content, nil +} + +// GetInternalProperty For zookeeper, getConfig and getConfigs have the same meaning. +func (c *MockDynamicConfiguration) GetInternalProperty(key string, opts ...Option) (string, error) { + return c.GetProperties(key, opts...) +} + +// GetRule ... +func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string, error) { + return c.GetProperties(key, opts...) +} + +// MockServiceConfigEvent ... func (c *MockDynamicConfiguration) MockServiceConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", @@ -130,6 +155,7 @@ func (c *MockDynamicConfiguration) MockServiceConfigEvent() { c.listener[key].Process(&ConfigChangeEvent{Key: key, Value: string(value), ConfigType: remoting.EventTypeAdd}) } +// MockApplicationConfigEvent ... func (c *MockDynamicConfiguration) MockApplicationConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", 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 1ce6594017ddc3cf76ba01c985b01a97e5ec91f3..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" ) @@ -36,18 +35,22 @@ import ( ) const ( + // ScopeApplication ... ScopeApplication = "application" - GeneralType = "general" + // GeneralType ... + GeneralType = "general" ) +// ConfigurationParser ... type ConfigurationParser interface { Parse(string) (map[string]string, error) ParseToUrls(content string) ([]*common.URL, error) } -//for support properties file in config center +// DefaultConfigurationParser for support properties file in config center type DefaultConfigurationParser struct{} +// ConfiguratorConfig ... type ConfiguratorConfig struct { ConfigVersion string `yaml:"configVersion"` Scope string `yaml:"scope"` @@ -56,6 +59,7 @@ type ConfiguratorConfig struct { Configs []ConfigItem `yaml:"configs"` } +// ConfigItem ... type ConfigItem struct { Type string `yaml:"type"` Enabled bool `yaml:"enabled"` @@ -67,15 +71,17 @@ type ConfigItem struct { Side string `yaml:"side"` } +// Parse ... func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) { - properties, err := properties.LoadString(content) + pps, err := properties.LoadString(content) if err != nil { logger.Errorf("Parse the content {%v} in DefaultConfigurationParser error ,error message is {%v}", content, err) return nil, err } - return properties.Map(), nil + return pps.Map(), nil } +// ParseToUrls ... func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common.URL, error) { config := ConfiguratorConfig{} if err := yaml.Unmarshal([]byte(content), &config); err != nil { @@ -103,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 { @@ -132,22 +139,23 @@ 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 { - perrors.WithStack(err) + 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 { - perrors.WithStack(err) + return nil, perrors.WithStack(err) } urls = append(urls, &url) } } return urls, nil } + func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -178,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) } @@ -240,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/factory.go b/config_center/zookeeper/factory.go index 611f4b9785c38eac4181750eefcabfb39607135d..3f4690d4e0edb4a859d76bf0fd692ca54e1a7a6a 100644 --- a/config_center/zookeeper/factory.go +++ b/config_center/zookeeper/factory.go @@ -17,10 +17,6 @@ package zookeeper -import ( - "sync" -) - import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/extension" @@ -35,14 +31,8 @@ func init() { type zookeeperDynamicConfigurationFactory struct { } -var once sync.Once -var dynamicConfiguration *zookeeperDynamicConfiguration - func (f *zookeeperDynamicConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { - var err error - once.Do(func() { - dynamicConfiguration, err = newZookeeperDynamicConfiguration(url) - }) + dynamicConfiguration, err := newZookeeperDynamicConfiguration(url) if err != nil { return nil, err } diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index 84e4b54e237fabb5775bfd0dfeb7043f1794a7ae..404243d4751146d1edc9a61d51cbb81d73c2ffb1 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -24,8 +24,8 @@ import ( ) import ( + "github.com/dubbogo/go-zookeeper/zk" perrors "github.com/pkg/errors" - "github.com/samuel/go-zookeeper/zk" ) import ( @@ -37,7 +37,11 @@ import ( "github.com/apache/dubbo-go/remoting/zookeeper" ) -const ZkClient = "zk config_center" +const ( + // ZkClient + //zookeeper client name + ZkClient = "zk config_center" +) type zookeeperDynamicConfiguration struct { url *common.URL @@ -109,7 +113,7 @@ func (c *zookeeperDynamicConfiguration) RemoveListener(key string, listener conf c.cacheListener.RemoveListener(key, listener) } -func (c *zookeeperDynamicConfiguration) GetConfig(key string, opts ...config_center.Option) (string, error) { +func (c *zookeeperDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) { tmpOpts := &config_center.Options{} for _, opt := range opts { @@ -134,75 +138,79 @@ func (c *zookeeperDynamicConfiguration) GetConfig(key string, opts ...config_cen content, _, err := c.client.GetContent(c.rootPath + "/" + key) if err != nil { return "", perrors.WithStack(err) - } else { - return string(content), nil } + return string(content), nil } //For zookeeper, getConfig and getConfigs have the same meaning. -func (c *zookeeperDynamicConfiguration) GetConfigs(key string, opts ...config_center.Option) (string, error) { - return c.GetConfig(key, opts...) +func (c *zookeeperDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, error) { + return c.GetProperties(key, opts...) +} + +func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) { + return c.GetProperties(key, opts...) } func (c *zookeeperDynamicConfiguration) Parser() parser.ConfigurationParser { return c.parser } + func (c *zookeeperDynamicConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } -func (r *zookeeperDynamicConfiguration) ZkClient() *zookeeper.ZookeeperClient { - return r.client +func (c *zookeeperDynamicConfiguration) ZkClient() *zookeeper.ZookeeperClient { + return c.client } -func (r *zookeeperDynamicConfiguration) SetZkClient(client *zookeeper.ZookeeperClient) { - r.client = client +func (c *zookeeperDynamicConfiguration) SetZkClient(client *zookeeper.ZookeeperClient) { + c.client = client } -func (r *zookeeperDynamicConfiguration) ZkClientLock() *sync.Mutex { - return &r.cltLock +func (c *zookeeperDynamicConfiguration) ZkClientLock() *sync.Mutex { + return &c.cltLock } -func (r *zookeeperDynamicConfiguration) WaitGroup() *sync.WaitGroup { - return &r.wg +func (c *zookeeperDynamicConfiguration) WaitGroup() *sync.WaitGroup { + return &c.wg } -func (r *zookeeperDynamicConfiguration) GetDone() chan struct{} { - return r.done +func (c *zookeeperDynamicConfiguration) Done() chan struct{} { + return c.done } -func (r *zookeeperDynamicConfiguration) GetUrl() common.URL { - return *r.url +func (c *zookeeperDynamicConfiguration) GetUrl() common.URL { + return *c.url } -func (r *zookeeperDynamicConfiguration) Destroy() { - if r.listener != nil { - r.listener.Close() +func (c *zookeeperDynamicConfiguration) Destroy() { + if c.listener != nil { + c.listener.Close() } - close(r.done) - r.wg.Wait() - r.closeConfigs() + close(c.done) + c.wg.Wait() + c.closeConfigs() } -func (r *zookeeperDynamicConfiguration) IsAvailable() bool { +func (c *zookeeperDynamicConfiguration) IsAvailable() bool { select { - case <-r.done: + case <-c.done: return false default: return true } } -func (r *zookeeperDynamicConfiguration) closeConfigs() { - r.cltLock.Lock() - defer r.cltLock.Unlock() +func (c *zookeeperDynamicConfiguration) closeConfigs() { + c.cltLock.Lock() + defer c.cltLock.Unlock() logger.Infof("begin to close provider zk client") // Close the old client first to close the tmp node - r.client.Close() - r.client = nil + c.client.Close() + c.client = nil } -func (r *zookeeperDynamicConfiguration) RestartCallBack() bool { +func (c *zookeeperDynamicConfiguration) RestartCallBack() bool { return true } diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go index 2f620457f75b7e35f713423e3841d0272cbd0730..22e15193cba1b533a2b1b965a44bf9665a6a4e5e 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -17,14 +17,13 @@ package zookeeper import ( - "context" "fmt" "sync" "testing" ) import ( - "github.com/samuel/go-zookeeper/zk" + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) @@ -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,10 +77,11 @@ 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() - configs, err := reg.GetConfig("dubbo.properties", config_center.WithGroup("dubbo")) + configs, err := reg.GetProperties("dubbo.properties", config_center.WithGroup("dubbo")) assert.NoError(t, err) m, err := reg.Parser().Parse(configs) assert.NoError(t, err) diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go index 7128b6f5a39e243840a1076f9fc506d94c7ed2ed..122dfaf4f268a706151de6acdaa78bb46e59f8fb 100644 --- a/config_center/zookeeper/listener.go +++ b/config_center/zookeeper/listener.go @@ -27,25 +27,30 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// CacheListener ... type CacheListener struct { keyListeners sync.Map rootPath string } +// NewCacheListener ... func NewCacheListener(rootPath string) *CacheListener { return &CacheListener{rootPath: rootPath} } + +// AddListener ... func (l *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) { // reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure // make a map[your type]struct{} like set in java - listeners, loaded := l.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{listener: struct{}{}}) + listeners, loaded := l.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{listener: {}}) if loaded { listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{} l.keyListeners.Store(key, listeners) } } +// RemoveListener ... func (l *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) { listeners, loaded := l.keyListeners.Load(key) if loaded { @@ -53,6 +58,7 @@ func (l *CacheListener) RemoveListener(key string, listener config_center.Config } } +// DataChange ... func (l *CacheListener) DataChange(event remoting.Event) bool { if event.Content == "" { //meanings new node diff --git a/contributing.md b/contributing.md index b1265c2351789d4929d81556d72234806aed6afa..9ee2dae32fad6caaf9e19c5e98e8b99b61c26a51 100644 --- a/contributing.md +++ b/contributing.md @@ -28,4 +28,14 @@ The title format of the pull request `MUST` follow the following rules: ### 3.1 log -> 1 when logging the function's input parameter, you should add '@' before input parameter name. +>- 1 when logging the function's input parameter, you should add '@' before input parameter name. + +### 3.2 naming + +>- 1 do not use an underscore in package name, such as `filter_impl`. +>- 2 do not use an underscore in constants, such as `DUBBO_PROTOCOL`. use 'DubboProtocol' instead. + +### 3.3 comment + +>- 1 there should be comment for every export func/var. +>- 2 the comment should begin with function name/var name. \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..40d4157b31d13ed8fd8b1ba8cc9d16b53638ac6a --- /dev/null +++ b/filter/access_key.go @@ -0,0 +1,39 @@ +/* + * 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 ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +type AccessKeyPair struct { + AccessKey string `yaml:"accessKey" json:"accessKey,omitempty" property:"accessKey"` + SecretKey string `yaml:"secretKey" json:"secretKey,omitempty" property:"secretKey"` + ConsumerSide string `yaml:"consumerSide" json:"ConsumerSide,consumerSide" property:"consumerSide"` + ProviderSide string `yaml:"providerSide" json:"providerSide,omitempty" property:"providerSide"` + Creator string `yaml:"creator" json:"creator,omitempty" property:"creator"` + Options string `yaml:"options" json:"options,omitempty" property:"options"` +} + +// AccessKeyStorage +// This SPI Extension support us to store our AccessKeyPair or load AccessKeyPair from other +// storage, such as filesystem. +type AccessKeyStorage interface { + GetAccessKeyPair(protocol.Invocation, *common.URL) *AccessKeyPair +} diff --git a/cluster/router.go b/filter/authenticator.go similarity index 74% rename from cluster/router.go rename to filter/authenticator.go index 54a19695574f245fcac236e9308a2469f306a4f8..ac2c8601d4a0d2e5ae3aed56415d9d23856cb502 100644 --- a/cluster/router.go +++ b/filter/authenticator.go @@ -15,27 +15,21 @@ * limitations under the License. */ -package cluster +package filter import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) -// Extension - Router +// Authenticator +type Authenticator interface { -type RouterFactory interface { - Router(*common.URL) (Router, error) -} - -type Router interface { - Route([]protocol.Invoker, common.URL, protocol.Invocation) []protocol.Invoker -} - -type RouterChain struct { - routers []Router -} - -func NewRouterChain(url common.URL) { + // Sign + // give a sign to request + Sign(protocol.Invocation, *common.URL) error + // Authenticate + // verify the signature of the request is valid or not + Authenticate(protocol.Invocation, *common.URL) error } diff --git a/filter/filter.go b/filter/filter.go index 5bd78998a76a1b0e8af99b0b3f0d7e6c103bb794..c069510498c7ac68b2bb2169dfe7132a4ef63229 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -17,12 +17,16 @@ package filter +import ( + "context" +) import ( "github.com/apache/dubbo-go/protocol" ) +// Filter // Extension - Filter type Filter interface { - Invoke(protocol.Invoker, protocol.Invocation) protocol.Result - OnResponse(protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result + Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result + OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result } diff --git a/filter/impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go similarity index 89% rename from filter/impl/access_log_filter.go rename to filter/filter_impl/access_log_filter.go index 89fa34952f99057f1d8bb35794a57f9905f5f169..fbfe7565170c7df468f755a4bd1aadde166a79c1 100644 --- a/filter/impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "os" "reflect" "strings" @@ -34,13 +35,21 @@ import ( const ( //used in URL. - FileDateFormat = "2006-01-02" + + // FileDateFormat ... + FileDateFormat = "2006-01-02" + // MessageDateLayout ... MessageDateLayout = "2006-01-02 15:04:05" - LogMaxBuffer = 5000 - LogFileMode = 0600 + // LogMaxBuffer ... + LogMaxBuffer = 5000 + // LogFileMode ... + LogFileMode = 0600 // those fields are the data collected by this filter - Types = "types" + + // Types ... + Types = "types" + // Arguments ... Arguments = "arguments" ) @@ -49,6 +58,7 @@ func init() { } /* + * AccessLogFilter * Although the access log filter is a default filter, * you should config "accesslog" in service's config to tell the filter where store the access log. * for example: @@ -66,13 +76,14 @@ type AccessLogFilter struct { logChan chan AccessLogData } -func (ef *AccessLogFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { accessLog := invoker.GetUrl().GetParam(constant.ACCESS_LOG_KEY, "") if len(accessLog) > 0 { accessLogData := AccessLogData{data: ef.buildAccessLogData(invoker, invocation), accessLog: accessLog} ef.logIntoChannel(accessLogData) } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } // it won't block the invocation @@ -86,7 +97,7 @@ func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) { } } -func (ef *AccessLogFilter) buildAccessLogData(invoker protocol.Invoker, invocation protocol.Invocation) map[string]string { +func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string { dataMap := make(map[string]string, 16) attachments := invocation.Attachments() dataMap[constant.INTERFACE_KEY] = attachments[constant.INTERFACE_KEY] @@ -119,7 +130,8 @@ func (ef *AccessLogFilter) buildAccessLogData(invoker protocol.Invoker, invocati return dataMap } -func (ef *AccessLogFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (ef *AccessLogFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } @@ -172,6 +184,7 @@ func isDefault(accessLog string) bool { return strings.EqualFold("true", accessLog) || strings.EqualFold("default", accessLog) } +// GetAccessLogFilter ... func GetAccessLogFilter() filter.Filter { accessLogFilter := &AccessLogFilter{logChan: make(chan AccessLogData, LogMaxBuffer)} go func() { @@ -182,6 +195,7 @@ func GetAccessLogFilter() filter.Filter { return accessLogFilter } +// AccessLogData ... type AccessLogData struct { accessLog string data map[string]string diff --git a/filter/impl/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go similarity index 84% rename from filter/impl/access_log_filter_test.go rename to filter/filter_impl/access_log_filter_test.go index 834d531f05f952c41abfe8e1c56c20c0285926b8..f0de24d2a89f35876a32763eeb75495e8919ecd9 100644 --- a/filter/impl/access_log_filter_test.go +++ b/filter/filter_impl/access_log_filter_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package filter_impl import ( "context" @@ -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) @@ -49,18 +49,18 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) accessLogFilter := GetAccessLogFilter() - result := accessLogFilter.Invoke(invoker, inv) + result := accessLogFilter.Invoke(context.Background(), invoker, inv) assert.Nil(t, result.Error()) } 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) @@ -70,13 +70,13 @@ func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) { inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) accessLogFilter := GetAccessLogFilter() - result := accessLogFilter.Invoke(invoker, inv) + result := accessLogFilter.Invoke(context.Background(), invoker, inv) assert.Nil(t, result.Error()) } func TestAccessLogFilter_OnResponse(t *testing.T) { result := &protocol.RPCResult{} accessLogFilter := GetAccessLogFilter() - response := accessLogFilter.OnResponse(result, nil, nil) + response := accessLogFilter.OnResponse(nil, result, nil, nil) assert.Equal(t, result, response) } diff --git a/filter/filter_impl/active_filter.go b/filter/filter_impl/active_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..23f2c8e25609dff89392107251715fe6f5175f09 --- /dev/null +++ b/filter/filter_impl/active_filter.go @@ -0,0 +1,70 @@ +/* + * 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_impl + +import ( + "context" + "strconv" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" + invocation2 "github.com/apache/dubbo-go/protocol/invocation" +) + +const ( + active = "active" + dubboInvokeStartTime = "dubboInvokeStartTime" +) + +func init() { + extension.SetFilter(active, GetActiveFilter) +} + +// ActiveFilter ... +type ActiveFilter struct { +} + +// Invoke ... +func (ef *ActiveFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking active filter. %v,%v", invocation.MethodName(), len(invocation.Arguments())) + invocation.(*invocation2.RPCInvocation).SetAttachments(dubboInvokeStartTime, strconv.FormatInt(protocol.CurrentTimeMillis(), 10)) + protocol.BeginCount(invoker.GetUrl(), invocation.MethodName()) + return invoker.Invoke(ctx, invocation) +} + +// OnResponse ... +func (ef *ActiveFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + startTime, err := strconv.ParseInt(invocation.(*invocation2.RPCInvocation).AttachmentsByKey(dubboInvokeStartTime, "0"), 10, 64) + if err != nil { + result.SetError(err) + logger.Errorf("parse dubbo_invoke_start_time to int64 failed") + return result + } + elapsed := protocol.CurrentTimeMillis() - startTime + protocol.EndCount(invoker.GetUrl(), invocation.MethodName(), elapsed, result.Error() == nil) + return result +} + +// GetActiveFilter ... +func GetActiveFilter() filter.Filter { + return &ActiveFilter{} +} diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8917e9141cad4f22ea201a9a07c2873b584c1f92 --- /dev/null +++ b/filter/filter_impl/active_filter_test.go @@ -0,0 +1,66 @@ +package filter_impl + +import ( + "context" + "errors" + "strconv" + "testing" +) + +import ( + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/mock" +) + +func TestActiveFilter_Invoke(t *testing.T) { + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0)) + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + filter := ActiveFilter{} + ctrl := gomock.NewController(t) + defer ctrl.Finish() + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().Invoke(gomock.Any()).Return(nil) + invoker.EXPECT().GetUrl().Return(url).Times(1) + filter.Invoke(context.Background(), invoker, invoc) + assert.True(t, invoc.AttachmentsByKey(dubboInvokeStartTime, "") != "") + +} + +func TestActiveFilter_OnResponse(t *testing.T) { + c := protocol.CurrentTimeMillis() + elapsed := 100 + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + dubboInvokeStartTime: strconv.FormatInt(c-int64(elapsed), 10), + }) + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + filter := ActiveFilter{} + ctrl := gomock.NewController(t) + defer ctrl.Finish() + invoker := mock.NewMockInvoker(ctrl) + invoker.EXPECT().GetUrl().Return(url).Times(1) + result := &protocol.RPCResult{ + Err: errors.New("test"), + } + filter.OnResponse(nil, result, invoker, invoc) + methodStatus := protocol.GetMethodStatus(url, "test") + urlStatus := protocol.GetURLStatus(url) + + assert.Equal(t, int32(1), methodStatus.GetTotal()) + assert.Equal(t, int32(1), urlStatus.GetTotal()) + assert.Equal(t, int32(1), methodStatus.GetFailed()) + assert.Equal(t, int32(1), urlStatus.GetFailed()) + assert.Equal(t, int32(1), methodStatus.GetSuccessiveRequestFailureCount()) + assert.Equal(t, int32(1), urlStatus.GetSuccessiveRequestFailureCount()) + assert.True(t, methodStatus.GetFailedElapsed() >= int64(elapsed)) + assert.True(t, urlStatus.GetFailedElapsed() >= int64(elapsed)) + assert.True(t, urlStatus.GetLastRequestFailedTimestamp() != int64(0)) + assert.True(t, methodStatus.GetLastRequestFailedTimestamp() != int64(0)) + +} diff --git a/filter/impl/active_filter.go b/filter/filter_impl/auth/accesskey_storage.go similarity index 55% rename from filter/impl/active_filter.go rename to filter/filter_impl/auth/accesskey_storage.go index 36a4e1a767ab7170ce8e5bebf2cfa4403f6ad4ff..5adb9d9ee37329228d1d02dc8802deeede68d327 100644 --- a/filter/impl/active_filter.go +++ b/filter/filter_impl/auth/accesskey_storage.go @@ -15,37 +15,34 @@ * limitations under the License. */ -package impl +package auth import ( + "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/filter" "github.com/apache/dubbo-go/protocol" ) -const active = "active" - -func init() { - extension.SetFilter(active, GetActiveFilter) +// DefaultAccesskeyStorage +// The default implementation of AccesskeyStorage +type DefaultAccesskeyStorage struct { } -type ActiveFilter struct { +// GetAccessKeyPair +// get AccessKeyPair from url by the key "accessKeyId" and "secretAccessKey" +func (storage *DefaultAccesskeyStorage) GetAccessKeyPair(invocation protocol.Invocation, url *common.URL) *filter.AccessKeyPair { + return &filter.AccessKeyPair{ + AccessKey: url.GetParam(constant.ACCESS_KEY_ID_KEY, ""), + SecretKey: url.GetParam(constant.SECRET_ACCESS_KEY_KEY, ""), + } } -func (ef *ActiveFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { - logger.Infof("invoking active filter. %v,%v", invocation.MethodName(), len(invocation.Arguments())) - - protocol.BeginCount(invoker.GetUrl(), invocation.MethodName()) - return invoker.Invoke(invocation) -} - -func (ef *ActiveFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { - - protocol.EndCount(invoker.GetUrl(), invocation.MethodName()) - return result +func init() { + extension.SetAccesskeyStorages(constant.DEFAULT_ACCESS_KEY_STORAGE, GetDefaultAccesskeyStorage) } -func GetActiveFilter() filter.Filter { - return &ActiveFilter{} +func GetDefaultAccesskeyStorage() filter.AccessKeyStorage { + return &DefaultAccesskeyStorage{} } diff --git a/filter/filter_impl/auth/accesskey_storage_test.go b/filter/filter_impl/auth/accesskey_storage_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aa566b81761d1f51a5b9141f8f10e66844f272bb --- /dev/null +++ b/filter/filter_impl/auth/accesskey_storage_test.go @@ -0,0 +1,45 @@ +/* + * 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 ( + "net/url" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + invocation2 "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestDefaultAccesskeyStorage_GetAccesskeyPair(t *testing.T) { + url := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SECRET_ACCESS_KEY_KEY, "skey"), + common.WithParamsValue(constant.ACCESS_KEY_ID_KEY, "akey")) + invocation := &invocation2.RPCInvocation{} + storage := GetDefaultAccesskeyStorage() + accesskeyPair := storage.GetAccessKeyPair(invocation, url) + assert.Equal(t, "skey", accesskeyPair.SecretKey) + assert.Equal(t, "akey", accesskeyPair.AccessKey) +} diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go new file mode 100644 index 0000000000000000000000000000000000000000..062744771acf8ccd505265875a103d24afeb06af --- /dev/null +++ b/filter/filter_impl/auth/consumer_sign.go @@ -0,0 +1,61 @@ +/* + * 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" +) +import ( + "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/filter" + "github.com/apache/dubbo-go/protocol" +) + +// ConsumerSignFilter +// This filter is working for signing the request on consumer side +type ConsumerSignFilter struct { +} + +func init() { + extension.SetFilter(constant.CONSUMER_SIGN_FILTER, getConsumerSignFilter) +} + +func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking ConsumerSign filter.") + url := invoker.GetUrl() + + err := doAuthWork(&url, func(authenticator filter.Authenticator) error { + return authenticator.Sign(invocation, &url) + }) + if err != nil { + panic(fmt.Sprintf("Sign for invocation %s # %s failed", url.ServiceKey(), invocation.MethodName())) + + } + return invoker.Invoke(ctx, invocation) +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..b02380e28f51356efae385a2e20a6b1ee4e9aa5c --- /dev/null +++ b/filter/filter_impl/auth/consumer_sign_test.go @@ -0,0 +1,54 @@ +/* + * 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" + "testing" +) + +import ( + "github.com/golang/mock/gomock" + "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" + "github.com/apache/dubbo-go/protocol/mock" +) + +func TestConsumerSignFilter_Invoke(t *testing.T) { + 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) + filter := &ConsumerSignFilter{} + ctrl := gomock.NewController(t) + defer ctrl.Finish() + invoker := mock.NewMockInvoker(ctrl) + result := &protocol.RPCResult{} + invoker.EXPECT().Invoke(inv).Return(result).Times(2) + invoker.EXPECT().GetUrl().Return(url).Times(2) + assert.Equal(t, result, filter.Invoke(context.Background(), invoker, inv)) + + url.SetParam(constant.SERVICE_AUTH_KEY, "true") + assert.Equal(t, result, filter.Invoke(context.Background(), invoker, inv)) +} diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go new file mode 100644 index 0000000000000000000000000000000000000000..2b8d55927807407f350ecc6cfc28b6913a6d1a81 --- /dev/null +++ b/filter/filter_impl/auth/default_authenticator.go @@ -0,0 +1,137 @@ +/* + * 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 ( + "errors" + "fmt" + "github.com/apache/dubbo-go/filter" + "strconv" + "time" +) + +import ( + "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/protocol" + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" +) + +func init() { + extension.SetAuthenticator(constant.DEFAULT_AUTHENTICATOR, GetDefaultAuthenticator) +} + +// DefaultAuthenticator +// The default implemetation of Authenticator +type DefaultAuthenticator struct { +} + +// Sign +// add the signature for the invocation +func (authenticator *DefaultAuthenticator) Sign(invocation protocol.Invocation, url *common.URL) error { + currentTimeMillis := strconv.Itoa(int(time.Now().Unix() * 1000)) + + consumer := url.GetParam(constant.APPLICATION_KEY, "") + accessKeyPair, err := getAccessKeyPair(invocation, url) + if err != nil { + return errors.New("get accesskey pair failed, cause: " + err.Error()) + } + inv := invocation.(*invocation_impl.RPCInvocation) + signature, err := getSignature(url, invocation, accessKeyPair.SecretKey, currentTimeMillis) + if err != nil { + return err + } + inv.SetAttachments(constant.REQUEST_SIGNATURE_KEY, signature) + inv.SetAttachments(constant.REQUEST_TIMESTAMP_KEY, currentTimeMillis) + inv.SetAttachments(constant.AK_KEY, accessKeyPair.AccessKey) + inv.SetAttachments(constant.CONSUMER, consumer) + return nil +} + +// getSignature +// get signature by the metadata and params of the invocation +func getSignature(url *common.URL, invocation protocol.Invocation, secrectKey string, currentTime string) (string, error) { + + requestString := fmt.Sprintf(constant.SIGNATURE_STRING_FORMAT, + url.ColonSeparatedKey(), invocation.MethodName(), secrectKey, currentTime) + var signature string + if parameterEncrypt := url.GetParamBool(constant.PARAMTER_SIGNATURE_ENABLE_KEY, false); parameterEncrypt { + var err error + if signature, err = SignWithParams(invocation.Arguments(), requestString, secrectKey); err != nil { + // TODO + return "", errors.New("sign the request with params failed, cause:" + err.Error()) + } + } else { + signature = Sign(requestString, secrectKey) + } + + return signature, nil +} + +// Authenticate +// This method verifies whether the signature sent by the requester is correct +func (authenticator *DefaultAuthenticator) Authenticate(invocation protocol.Invocation, url *common.URL) error { + accessKeyId := invocation.AttachmentsByKey(constant.AK_KEY, "") + + requestTimestamp := invocation.AttachmentsByKey(constant.REQUEST_TIMESTAMP_KEY, "") + originSignature := invocation.AttachmentsByKey(constant.REQUEST_SIGNATURE_KEY, "") + consumer := invocation.AttachmentsByKey(constant.CONSUMER, "") + if IsEmpty(accessKeyId, false) || IsEmpty(consumer, false) || + IsEmpty(requestTimestamp, false) || IsEmpty(originSignature, false) { + return errors.New("failed to authenticate your ak/sk, maybe the consumer has not enabled the auth") + } + + accessKeyPair, err := getAccessKeyPair(invocation, url) + if err != nil { + return errors.New("failed to authenticate , can't load the accessKeyPair") + } + + computeSignature, err := getSignature(url, invocation, accessKeyPair.SecretKey, requestTimestamp) + if err != nil { + return err + } + if success := computeSignature == originSignature; !success { + return errors.New("failed to authenticate, signature is not correct") + } + return nil +} + +func getAccessKeyPair(invocation protocol.Invocation, url *common.URL) (*filter.AccessKeyPair, error) { + accesskeyStorage := extension.GetAccesskeyStorages(url.GetParam(constant.ACCESS_KEY_STORAGE_KEY, constant.DEFAULT_ACCESS_KEY_STORAGE)) + accessKeyPair := accesskeyStorage.GetAccessKeyPair(invocation, url) + if accessKeyPair == nil || IsEmpty(accessKeyPair.AccessKey, false) || IsEmpty(accessKeyPair.SecretKey, true) { + return nil, errors.New("accessKeyId or secretAccessKey not found") + } else { + return accessKeyPair, nil + } +} + +func GetDefaultAuthenticator() filter.Authenticator { + return &DefaultAuthenticator{} +} + +func doAuthWork(url *common.URL, do func(filter.Authenticator) error) error { + + shouldAuth := url.GetParamBool(constant.SERVICE_AUTH_KEY, false) + if shouldAuth { + authenticator := extension.GetAuthenticator(url.GetParam(constant.AUTHENTICATOR_KEY, constant.DEFAULT_AUTHENTICATOR)) + return do(authenticator) + } + return nil +} diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5b107b5960ff5adc383d52aa5e393d9fc6e71d14 --- /dev/null +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -0,0 +1,147 @@ +/* + * 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 ( + "fmt" + "net/url" + "strconv" + "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/invocation" +) + +func TestDefaultAuthenticator_Authenticate(t *testing.T) { + secret := "dubbo-sk" + access := "dubbo-ak" + 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) + parmas := []interface{}{"OK", struct { + Name string + Id int64 + }{"YUYU", 1}} + inv := invocation.NewRPCInvocation("test", parmas, nil) + requestTime := strconv.Itoa(int(time.Now().Unix() * 1000)) + signature, _ := getSignature(&testurl, inv, secret, requestTime) + + var authenticator = &DefaultAuthenticator{} + + invcation := invocation.NewRPCInvocation("test", parmas, map[string]string{ + constant.REQUEST_SIGNATURE_KEY: signature, + constant.CONSUMER: "test", + constant.REQUEST_TIMESTAMP_KEY: requestTime, + constant.AK_KEY: access, + }) + err := authenticator.Authenticate(invcation, &testurl) + assert.Nil(t, err) + // modify the params + invcation = invocation.NewRPCInvocation("test", parmas[:1], map[string]string{ + constant.REQUEST_SIGNATURE_KEY: signature, + constant.CONSUMER: "test", + constant.REQUEST_TIMESTAMP_KEY: requestTime, + constant.AK_KEY: access, + }) + err = authenticator.Authenticate(invcation, &testurl) + assert.NotNil(t, err) + +} + +func TestDefaultAuthenticator_Sign(t *testing.T) { + authenticator := &DefaultAuthenticator{} + 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") + inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, nil) + _ = authenticator.Sign(inv, &testurl) + assert.NotEqual(t, inv.AttachmentsByKey(constant.REQUEST_SIGNATURE_KEY, ""), "") + assert.NotEqual(t, inv.AttachmentsByKey(constant.CONSUMER, ""), "") + assert.NotEqual(t, inv.AttachmentsByKey(constant.REQUEST_TIMESTAMP_KEY, ""), "") + assert.Equal(t, inv.AttachmentsByKey(constant.AK_KEY, ""), "akey") + +} + +func Test_getAccessKeyPairSuccess(t *testing.T) { + testurl := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SECRET_ACCESS_KEY_KEY, "skey"), + common.WithParamsValue(constant.ACCESS_KEY_ID_KEY, "akey")) + invcation := invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil) + _, e := getAccessKeyPair(invcation, testurl) + assert.Nil(t, e) +} + +func Test_getAccessKeyPairFailed(t *testing.T) { + defer func() { + e := recover() + assert.NotNil(t, e) + }() + testurl := common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.ACCESS_KEY_ID_KEY, "akey")) + invcation := invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil) + _, e := getAccessKeyPair(invcation, testurl) + assert.NotNil(t, e) + testurl = common.NewURLWithOptions( + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SECRET_ACCESS_KEY_KEY, "skey"), + common.WithParamsValue(constant.ACCESS_KEY_ID_KEY, "akey"), common.WithParamsValue(constant.ACCESS_KEY_STORAGE_KEY, "dubbo")) + _, e = getAccessKeyPair(invcation, testurl) + +} + +func Test_getSignatureWithinParams(t *testing.T) { + 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{ + "": "", + }) + secret := "dubbo" + current := strconv.Itoa(int(time.Now().Unix() * 1000)) + signature, _ := getSignature(&testurl, inv, secret, current) + requestString := fmt.Sprintf(constant.SIGNATURE_STRING_FORMAT, + testurl.ColonSeparatedKey(), inv.MethodName(), secret, current) + s, _ := SignWithParams(inv.Arguments(), requestString, secret) + assert.False(t, IsEmpty(signature, false)) + assert.Equal(t, s, signature) +} + +func Test_getSignature(t *testing.T) { + 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" + current := strconv.Itoa(int(time.Now().Unix() * 1000)) + signature, _ := getSignature(&testurl, inv, secret, current) + requestString := fmt.Sprintf(constant.SIGNATURE_STRING_FORMAT, + testurl.ColonSeparatedKey(), inv.MethodName(), secret, current) + s := Sign(requestString, secret) + assert.False(t, IsEmpty(signature, false)) + assert.Equal(t, s, signature) +} diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go new file mode 100644 index 0000000000000000000000000000000000000000..0d5772e5508894111a88443bfe2d1b02ebfac54a --- /dev/null +++ b/filter/filter_impl/auth/provider_auth.go @@ -0,0 +1,64 @@ +/* + * 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" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +// ProviderAuthFilter +// This filter is used to verify the correctness of the signature on provider side +type ProviderAuthFilter struct { +} + +func init() { + extension.SetFilter(constant.PROVIDER_AUTH_FILTER, getProviderAuthFilter) +} + +func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking providerAuth filter.") + url := invoker.GetUrl() + + err := doAuthWork(&url, func(authenticator filter.Authenticator) error { + return authenticator.Authenticate(invocation, &url) + }) + if err != nil { + logger.Infof("auth the request: %v occur exception, cause: %s", invocation, err.Error()) + return &protocol.RPCResult{ + Err: err, + } + } + + return invoker.Invoke(ctx, invocation) +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..626782ae8390f046f441c1f162700a883e6f22d0 --- /dev/null +++ b/filter/filter_impl/auth/provider_auth_test.go @@ -0,0 +1,74 @@ +/* + * 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" + "strconv" + "testing" + "time" +) + +import ( + "github.com/golang/mock/gomock" + "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" + "github.com/apache/dubbo-go/protocol/mock" +) + +func TestProviderAuthFilter_Invoke(t *testing.T) { + secret := "dubbo-sk" + access := "dubbo-ak" + 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{}{ + "OK", + struct { + Name string + Id int64 + }{"YUYU", 1}, + } + inv := invocation.NewRPCInvocation("test", parmas, nil) + requestTime := strconv.Itoa(int(time.Now().Unix() * 1000)) + signature, _ := getSignature(&url, inv, secret, requestTime) + + inv = invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + constant.REQUEST_SIGNATURE_KEY: signature, + constant.CONSUMER: "test", + constant.REQUEST_TIMESTAMP_KEY: requestTime, + constant.AK_KEY: access, + }) + ctrl := gomock.NewController(t) + filter := &ProviderAuthFilter{} + defer ctrl.Finish() + invoker := mock.NewMockInvoker(ctrl) + result := &protocol.RPCResult{} + invoker.EXPECT().Invoke(inv).Return(result).Times(2) + invoker.EXPECT().GetUrl().Return(url).Times(2) + assert.Equal(t, result, filter.Invoke(context.Background(), invoker, inv)) + url.SetParam(constant.SERVICE_AUTH_KEY, "true") + assert.Equal(t, result, filter.Invoke(context.Background(), invoker, inv)) + +} diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go new file mode 100644 index 0000000000000000000000000000000000000000..043a549a849dde66712e1bef389dd91a024660df --- /dev/null +++ b/filter/filter_impl/auth/sign_util.go @@ -0,0 +1,72 @@ +/* + * 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 ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "strings" +) + +// Sign +// get a signature string with given information, such as metadata or parameters +func Sign(metadata, key string) string { + return doSign([]byte(metadata), key) +} + +func SignWithParams(params []interface{}, metadata, key string) (string, error) { + if params == nil || len(params) == 0 { + return Sign(metadata, key), nil + } + + data := append(params, metadata) + if bytes, err := toBytes(data); err != nil { + // TODO + return "", errors.New("data convert to bytes failed") + } else { + return doSign(bytes, key), nil + } +} + +func toBytes(data []interface{}) ([]byte, error) { + if bytes, err := json.Marshal(data); err != nil { + return nil, errors.New("") + } else { + return bytes, nil + } +} + +func doSign(bytes []byte, key string) string { + mac := hmac.New(sha256.New, []byte(key)) + mac.Write(bytes) + signature := mac.Sum(nil) + return base64.URLEncoding.EncodeToString(signature) +} + +func IsEmpty(s string, allowSpace bool) bool { + if len(s) == 0 { + return true + } + if !allowSpace { + return strings.TrimSpace(s) == "" + } + return false +} diff --git a/filter/filter_impl/auth/sign_util_test.go b/filter/filter_impl/auth/sign_util_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a4aaf2da27a8dd14969e0e3f93eaee16dfc31b03 --- /dev/null +++ b/filter/filter_impl/auth/sign_util_test.go @@ -0,0 +1,101 @@ +/* + * 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 ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestIsEmpty(t *testing.T) { + type args struct { + s string + allowSpace bool + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add test cases. + {"test1", args{s: " ", allowSpace: false}, true}, + {"test2", args{s: " ", allowSpace: true}, false}, + {"test3", args{s: "hello,dubbo", allowSpace: false}, false}, + {"test4", args{s: "hello,dubbo", allowSpace: true}, false}, + {"test5", args{s: "", allowSpace: true}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsEmpty(tt.args.s, tt.args.allowSpace); got != tt.want { + t.Errorf("IsEmpty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSign(t *testing.T) { + metadata := "com.ikurento.user.UserProvider::sayHi" + key := "key" + signature := Sign(metadata, key) + assert.NotNil(t, signature) + +} + +func TestSignWithParams(t *testing.T) { + metadata := "com.ikurento.user.UserProvider::sayHi" + key := "key" + params := []interface{}{ + "a", 1, struct { + Name string + Id int64 + }{"YuYu", 1}, + } + signature, _ := SignWithParams(params, metadata, key) + assert.False(t, IsEmpty(signature, false)) +} + +func Test_doSign(t *testing.T) { + sign := doSign([]byte("DubboGo"), "key") + sign1 := doSign([]byte("DubboGo"), "key") + sign2 := doSign([]byte("DubboGo"), "key2") + assert.NotNil(t, sign) + assert.Equal(t, sign1, sign) + assert.NotEqual(t, sign1, sign2) +} + +func Test_toBytes(t *testing.T) { + params := []interface{}{ + "a", 1, struct { + Name string + Id int64 + }{"YuYu", 1}, + } + params2 := []interface{}{ + "a", 1, struct { + Name string + Id int64 + }{"YuYu", 1}, + } + jsonBytes, _ := toBytes(params) + jsonBytes2, _ := toBytes(params2) + assert.NotNil(t, jsonBytes) + assert.Equal(t, jsonBytes, jsonBytes2) +} diff --git a/filter/impl/echo_filter.go b/filter/filter_impl/echo_filter.go similarity index 79% rename from filter/impl/echo_filter.go rename to filter/filter_impl/echo_filter.go index 18e42c8cb2b15acb27573c5e24f11a8b69e0d496..a12800a21a8ebe4545b4a8b5bd0f8a30c1462105 100644 --- a/filter/impl/echo_filter.go +++ b/filter/filter_impl/echo_filter.go @@ -15,7 +15,11 @@ * limitations under the License. */ -package impl +package filter_impl + +import ( + "context" +) import ( "github.com/apache/dubbo-go/common/constant" @@ -26,6 +30,7 @@ import ( ) const ( + // ECHO echo module name ECHO = "echo" ) @@ -33,12 +38,14 @@ func init() { extension.SetFilter(ECHO, GetFilter) } +// EchoFilter // RPCService need a Echo method in consumer, if you want to use EchoFilter // eg: // Echo func(ctx context.Context, arg interface{}, rsp *Xxx) error type EchoFilter struct{} -func (ef *EchoFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (ef *EchoFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking echo filter.") logger.Debugf("%v,%v", invocation.MethodName(), len(invocation.Arguments())) if invocation.MethodName() == constant.ECHO && len(invocation.Arguments()) == 1 { @@ -48,13 +55,17 @@ func (ef *EchoFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invoc } } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (ef *EchoFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, + _ protocol.Invocation) protocol.Result { + return result } +// GetFilter ... func GetFilter() filter.Filter { return &EchoFilter{} } diff --git a/filter/impl/echo_filter_test.go b/filter/filter_impl/echo_filter_test.go similarity index 77% rename from filter/impl/echo_filter_test.go rename to filter/filter_impl/echo_filter_test.go index e2e592974701ad18c5b01e884485c022ee2320b8..fc09bdce696c6be3c9e11d0ac864b187d1d85cde 100644 --- a/filter/impl/echo_filter_test.go +++ b/filter/filter_impl/echo_filter_test.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "testing" ) @@ -33,12 +34,10 @@ import ( func TestEchoFilter_Invoke(t *testing.T) { filter := GetFilter() - result := filter.Invoke(protocol.NewBaseInvoker(common.URL{}), - invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil)) assert.Equal(t, "OK", result.Result()) - result = filter.Invoke(protocol.NewBaseInvoker(common.URL{}), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil)) + result = filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } diff --git a/filter/impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go similarity index 78% rename from filter/impl/execute_limit_filter.go rename to filter/filter_impl/execute_limit_filter.go index 156af1b140283dd76c4867ca26e9b42ce8eb25c0..434c378045456eb13317e0a48630ebd33f244c05 100644 --- a/filter/impl/execute_limit_filter.go +++ b/filter/filter_impl/execute_limit_filter.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "strconv" "sync" "sync/atomic" @@ -32,17 +33,20 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/filter" - _ "github.com/apache/dubbo-go/filter/common/impl" + _ "github.com/apache/dubbo-go/filter/handler" "github.com/apache/dubbo-go/protocol" ) -const name = "execute" +const ( + name = "execute" +) func init() { extension.SetFilter(name, GetExecuteLimitFilter) } /** + * ExecuteLimitFilter * The filter will limit the number of in-progress request and it's thread-safe. * example: * "UserProvider": @@ -71,23 +75,25 @@ type ExecuteLimitFilter struct { executeState *concurrent.Map } +// ExecuteState ... type ExecuteState struct { concurrentCount int64 } -func (ef *ExecuteLimitFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (ef *ExecuteLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { methodConfigPrefix := "methods." + invocation.MethodName() + "." - url := invoker.GetUrl() - limitTarget := url.ServiceKey() + ivkURL := invoker.GetUrl() + limitTarget := ivkURL.ServiceKey() limitRateConfig := constant.DEFAULT_EXECUTE_LIMIT - methodLevelConfig := url.GetParam(methodConfigPrefix+constant.EXECUTE_LIMIT_KEY, "") + methodLevelConfig := ivkURL.GetParam(methodConfigPrefix+constant.EXECUTE_LIMIT_KEY, "") if len(methodLevelConfig) > 0 { // we have the method-level configuration limitTarget = limitTarget + "#" + invocation.MethodName() limitRateConfig = methodLevelConfig } else { - limitRateConfig = url.GetParam(constant.EXECUTE_LIMIT_KEY, constant.DEFAULT_EXECUTE_LIMIT) + limitRateConfig = ivkURL.GetParam(constant.EXECUTE_LIMIT_KEY, constant.DEFAULT_EXECUTE_LIMIT) } limitRate, err := strconv.ParseInt(limitRateConfig, 0, 0) @@ -97,7 +103,7 @@ func (ef *ExecuteLimitFilter) Invoke(invoker protocol.Invoker, invocation protoc } if limitRate < 0 { - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } state, _ := ef.executeState.LoadOrStore(limitTarget, &ExecuteState{ @@ -107,16 +113,17 @@ func (ef *ExecuteLimitFilter) Invoke(invoker protocol.Invoker, invocation protoc concurrentCount := state.(*ExecuteState).increase() defer state.(*ExecuteState).decrease() if concurrentCount > limitRate { - logger.Errorf("The invocation was rejected due to over the execute limitation, url: %s ", url.String()) - rejectedHandlerConfig := url.GetParam(methodConfigPrefix+constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, - url.GetParam(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, constant.DEFAULT_KEY)) - return extension.GetRejectedExecutionHandler(rejectedHandlerConfig).RejectedExecution(url, invocation) + logger.Errorf("The invocation was rejected due to over the execute limitation, url: %s ", ivkURL.String()) + rejectedHandlerConfig := ivkURL.GetParam(methodConfigPrefix+constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, + ivkURL.GetParam(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, constant.DEFAULT_KEY)) + return extension.GetRejectedExecutionHandler(rejectedHandlerConfig).RejectedExecution(ivkURL, invocation) } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (ef *ExecuteLimitFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (ef *ExecuteLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } @@ -131,6 +138,7 @@ func (state *ExecuteState) decrease() { var executeLimitOnce sync.Once var executeLimitFilter *ExecuteLimitFilter +// GetExecuteLimitFilter ... func GetExecuteLimitFilter() filter.Filter { executeLimitOnce.Do(func() { executeLimitFilter = &ExecuteLimitFilter{ diff --git a/filter/impl/execute_limit_filter_test.go b/filter/filter_impl/execute_limit_filter_test.go similarity index 88% rename from filter/impl/execute_limit_filter_test.go rename to filter/filter_impl/execute_limit_filter_test.go index 5d729c0e6a1205902856eccfa6aa96b0bee0e790..ae8641f2db0b98b59f9939cfc85f3ad096b1bc7f 100644 --- a/filter/impl/execute_limit_filter_test.go +++ b/filter/filter_impl/execute_limit_filter_test.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "net/url" "testing" ) @@ -43,7 +44,7 @@ func TestExecuteLimitFilter_Invoke_Ignored(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } @@ -60,7 +61,7 @@ func TestExecuteLimitFilter_Invoke_Configure_Error(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } @@ -77,7 +78,7 @@ func TestExecuteLimitFilter_Invoke(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } diff --git a/filter/impl/generic_filter.go b/filter/filter_impl/generic_filter.go similarity index 80% rename from filter/impl/generic_filter.go rename to filter/filter_impl/generic_filter.go index 35aadb11a444bda56109e238b17267f71ec2606b..9bc131ef8903942b84df2b8fc14fd11143d1a7b6 100644 --- a/filter/impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -15,15 +15,18 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "reflect" "strings" ) + import ( hessian "github.com/apache/dubbo-go-hessian2" ) + import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -33,6 +36,8 @@ import ( ) const ( + // GENERIC + //generic module name GENERIC = "generic" ) @@ -42,38 +47,43 @@ func init() { // when do a generic invoke, struct need to be map +// GenericFilter ... type GenericFilter struct{} -func (ef *GenericFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 { oldArguments := invocation.Arguments() - var newParams []hessian.Object + if oldParams, ok := oldArguments[2].([]interface{}); ok { + newParams := make([]hessian.Object, 0, len(oldParams)) for i := range oldParams { newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i]))) } - } else { - return invoker.Invoke(invocation) - } - newArguments := []interface{}{ - oldArguments[0], - oldArguments[1], - newParams, + newArguments := []interface{}{ + oldArguments[0], + oldArguments[1], + newParams, + } + newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments()) + newInvocation.SetReply(invocation.Reply()) + return invoker.Invoke(ctx, newInvocation) } - newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments()) - newInvocation.SetReply(invocation.Reply()) - return invoker.Invoke(newInvocation) } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (ef *GenericFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, + _ protocol.Invocation) protocol.Result { return result } +// GetGenericFilter ... func GetGenericFilter() filter.Filter { return &GenericFilter{} } + func struct2MapAll(obj interface{}) interface{} { if obj == nil { return obj @@ -118,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 == "" { @@ -127,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/impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go similarity index 99% rename from filter/impl/generic_filter_test.go rename to filter/filter_impl/generic_filter_test.go index 9797c40df1f57017241675013620a53320e475ad..b08229199898a30657682d47c32689dc084f5bf4 100644 --- a/filter/impl/generic_filter_test.go +++ b/filter/filter_impl/generic_filter_test.go @@ -15,12 +15,13 @@ * limitations under the License. */ -package impl +package filter_impl import ( "reflect" "testing" ) + import ( "github.com/stretchr/testify/assert" ) @@ -87,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.go b/filter/filter_impl/generic_service_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..6272df6b39b0c18a77721f3a8c9e92618133aa6c --- /dev/null +++ b/filter/filter_impl/generic_service_filter.go @@ -0,0 +1,133 @@ +/* + * 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_impl + +import ( + "context" + "reflect" + "strings" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/mitchellh/mapstructure" + 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/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" + invocation2 "github.com/apache/dubbo-go/protocol/invocation" +) + +const ( + // GENERIC_SERVICE ... + GENERIC_SERVICE = "generic_service" + // GENERIC_SERIALIZATION_DEFAULT ... + GENERIC_SERIALIZATION_DEFAULT = "true" +) + +func init() { + extension.SetFilter(GENERIC_SERVICE, GetGenericServiceFilter) +} + +// GenericServiceFilter ... +type GenericServiceFilter struct{} + +// Invoke ... +func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking generic service filter.") + logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments())) + + if invocation.MethodName() != constant.GENERIC || len(invocation.Arguments()) != 3 { + return invoker.Invoke(ctx, invocation) + } + + var ( + ok bool + err error + methodName string + newParams []interface{} + genericKey string + argsType []reflect.Type + oldParams []hessian.Object + ) + + url := invoker.GetUrl() + methodName = invocation.Arguments()[0].(string) + // get service + svc := common.ServiceMap.GetService(url.Protocol, strings.TrimPrefix(url.Path, "/")) + // get method + method := svc.Method()[methodName] + if method == nil { + logger.Errorf("[Generic Service Filter] Don't have this method: %s", methodName) + return &protocol.RPCResult{} + } + argsType = method.ArgsType() + genericKey = invocation.AttachmentsByKey(constant.GENERIC_KEY, GENERIC_SERIALIZATION_DEFAULT) + if genericKey == GENERIC_SERIALIZATION_DEFAULT { + oldParams, ok = invocation.Arguments()[2].([]hessian.Object) + } else { + logger.Errorf("[Generic Service Filter] Don't support this generic: %s", genericKey) + return &protocol.RPCResult{} + } + if !ok { + logger.Errorf("[Generic Service Filter] wrong serialization") + return &protocol.RPCResult{} + } + if len(oldParams) != len(argsType) { + logger.Errorf("[Generic Service Filter] method:%s invocation arguments number was wrong", methodName) + return &protocol.RPCResult{} + } + // oldParams convert to newParams + newParams = make([]interface{}, len(oldParams)) + for i := range argsType { + newParam := reflect.New(argsType[i]).Interface() + err = mapstructure.Decode(oldParams[i], newParam) + newParam = reflect.ValueOf(newParam).Elem().Interface() + if err != nil { + logger.Errorf("[Generic Service Filter] decode arguments map to struct wrong: error{%v}", perrors.WithStack(err)) + return &protocol.RPCResult{} + } + newParams[i] = newParam + } + newInvocation := invocation2.NewRPCInvocation(methodName, newParams, invocation.Attachments()) + newInvocation.SetReply(invocation.Reply()) + return invoker.Invoke(ctx, newInvocation) +} + +// OnResponse ... +func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil { + v := reflect.ValueOf(result.Result()) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + result.SetResult(struct2MapAll(v.Interface())) + } + return result +} + +// GetGenericServiceFilter ... +func GetGenericServiceFilter() filter.Filter { + return &GenericServiceFilter{} +} diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..37c6af7450a75449fce51182684be2f619eda9d8 --- /dev/null +++ b/filter/filter_impl/generic_service_filter_test.go @@ -0,0 +1,149 @@ +/* + * 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_impl + +import ( + "context" + "errors" + "reflect" + "testing" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/proxy/proxy_factory" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +type TestStruct struct { + AaAa string + BaBa string `m:"baBa"` + XxYy struct { + xxXx string `m:"xxXx"` + Xx string `m:"xx"` + } `m:"xxYy"` +} + +func (c *TestStruct) JavaClassName() string { + return "com.test.testStruct" +} + +type TestService struct{} + +// MethodOne ... +func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct, + test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) { + if test1 == nil { + return nil, errors.New("param test1 is nil") + } + if test2 == nil { + return nil, errors.New("param test2 is nil") + } + if test3 == nil { + return nil, errors.New("param test3 is nil") + } + if test4 == nil { + return nil, errors.New("param test4 is nil") + } + if test5 == nil { + return nil, errors.New("param test5 is nil") + } + return &TestStruct{}, nil +} + +// Reference ... +func (*TestService) Reference() string { + return "com.test.Path" +} + +func TestGenericServiceFilter_Invoke(t *testing.T) { + hessian.RegisterPOJO(&TestStruct{}) + methodName := "$invoke" + m := make(map[string]interface{}) + m["AaAa"] = "nihao" + x := make(map[string]interface{}) + x["xxXX"] = "nihaoxxx" + m["XxYy"] = x + aurguments := []interface{}{ + "MethodOne", + nil, + []hessian.Object{ + hessian.Object(m), + hessian.Object(append(make([]map[string]interface{}, 1), m)), + hessian.Object("111"), + hessian.Object(append(make([]map[string]interface{}, 1), m)), + hessian.Object("222")}, + } + s := &TestService{} + _, _ = common.ServiceMap.Register("testprotocol", s) + rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil) + filter := GetGenericServiceFilter() + 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()) +} + +func TestGenericServiceFilter_ResponseTestStruct(t *testing.T) { + ts := &TestStruct{ + AaAa: "aaa", + BaBa: "bbb", + XxYy: struct { + xxXx string `m:"xxXx"` + Xx string `m:"xx"` + }{}, + } + result := &protocol.RPCResult{ + Rest: ts, + } + aurguments := []interface{}{ + "MethodOne", + nil, + []hessian.Object{nil}, + } + filter := GetGenericServiceFilter() + methodName := "$invoke" + rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil) + r := filter.OnResponse(nil, result, nil, rpcInvocation) + assert.NotNil(t, r.Result()) + assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.Map) +} + +func TestGenericServiceFilter_ResponseString(t *testing.T) { + str := "111" + result := &protocol.RPCResult{ + Rest: str, + } + aurguments := []interface{}{ + "MethodOne", + nil, + []hessian.Object{nil}, + } + filter := GetGenericServiceFilter() + methodName := "$invoke" + rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil) + r := filter.OnResponse(nil, result, nil, rpcInvocation) + assert.NotNil(t, r.Result()) + assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.String) +} diff --git a/filter/filter_impl/graceful_shutdown_filter.go b/filter/filter_impl/graceful_shutdown_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..95e625b2d56895a4d57823e4e0e2e7d1d5e90a08 --- /dev/null +++ b/filter/filter_impl/graceful_shutdown_filter.go @@ -0,0 +1,87 @@ +/* + * 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_impl + +import ( + "context" + "sync/atomic" +) + +import ( + "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/config" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +func init() { + var consumerFiler = &gracefulShutdownFilter{ + shutdownConfig: config.GetConsumerConfig().ShutdownConfig, + } + var providerFilter = &gracefulShutdownFilter{ + shutdownConfig: config.GetProviderConfig().ShutdownConfig, + } + + extension.SetFilter(constant.CONSUMER_SHUTDOWN_FILTER, func() filter.Filter { + return consumerFiler + }) + + extension.SetFilter(constant.PROVIDER_SHUTDOWN_FILTER, func() filter.Filter { + return providerFilter + }) +} + +type gracefulShutdownFilter struct { + activeCount int32 + shutdownConfig *config.ShutdownConfig +} + +func (gf *gracefulShutdownFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + if gf.rejectNewRequest() { + logger.Info("The application is closing, new request will be rejected.") + return gf.getRejectHandler().RejectedExecution(invoker.GetUrl(), invocation) + } + atomic.AddInt32(&gf.activeCount, 1) + return invoker.Invoke(ctx, invocation) +} + +func (gf *gracefulShutdownFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + atomic.AddInt32(&gf.activeCount, -1) + // although this isn't thread safe, it won't be a problem if the gf.rejectNewRequest() is true. + if gf.shutdownConfig != nil && gf.activeCount <= 0 { + gf.shutdownConfig.RequestsFinished = true + } + return result +} + +func (gf *gracefulShutdownFilter) rejectNewRequest() bool { + if gf.shutdownConfig == nil { + return false + } + return gf.shutdownConfig.RejectRequest +} + +func (gf *gracefulShutdownFilter) getRejectHandler() filter.RejectedExecutionHandler { + handler := constant.DEFAULT_KEY + if gf.shutdownConfig != nil && len(gf.shutdownConfig.RejectRequestHandler) > 0 { + handler = gf.shutdownConfig.RejectRequestHandler + } + return extension.GetRejectedExecutionHandler(handler) +} diff --git a/filter/filter_impl/graceful_shutdown_filter_test.go b/filter/filter_impl/graceful_shutdown_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4c670933e3dcec29ad9ae7bfef250b4236ae7c54 --- /dev/null +++ b/filter/filter_impl/graceful_shutdown_filter_test.go @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter_impl + +import ( + "context" + "net/url" + "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/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/filter" + common2 "github.com/apache/dubbo-go/filter/handler" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestGenericFilter_Invoke(t *testing.T) { + invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]string, 0)) + + invokeUrl := common.NewURLWithOptions( + common.WithParams(url.Values{})) + + shutdownFilter := extension.GetFilter(constant.PROVIDER_SHUTDOWN_FILTER).(*gracefulShutdownFilter) + + providerConfig := config.GetProviderConfig() + + assert.False(t, shutdownFilter.rejectNewRequest()) + assert.Nil(t, providerConfig.ShutdownConfig) + + assert.Equal(t, extension.GetRejectedExecutionHandler(constant.DEFAULT_KEY), + shutdownFilter.getRejectHandler()) + + result := shutdownFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) + assert.NotNil(t, result) + assert.Nil(t, result.Error()) + + providerConfig.ShutdownConfig = &config.ShutdownConfig{ + RejectRequest: true, + RejectRequestHandler: "mock", + } + shutdownFilter.shutdownConfig = providerConfig.ShutdownConfig + + assert.True(t, shutdownFilter.rejectNewRequest()) + result = shutdownFilter.OnResponse(nil, nil, protocol.NewBaseInvoker(*invokeUrl), invoc) + + rejectHandler := &common2.OnlyLogRejectedExecutionHandler{} + extension.SetRejectedExecutionHandler("mock", func() filter.RejectedExecutionHandler { + return rejectHandler + }) + assert.True(t, providerConfig.ShutdownConfig.RequestsFinished) + assert.Equal(t, rejectHandler, shutdownFilter.getRejectHandler()) + +} diff --git a/filter/impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go similarity index 91% rename from filter/impl/hystrix_filter.go rename to filter/filter_impl/hystrix_filter.go index 3fd9f87168616b69d5ec72460767890d6956c154..9fd97b57b677c9aa8ec492151df9aace6dc78b62 100644 --- a/filter/impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -14,9 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package impl + +package filter_impl import ( + "context" "fmt" "regexp" "sync" @@ -35,9 +37,12 @@ import ( ) const ( + // HYSTRIX_CONSUMER ... HYSTRIX_CONSUMER = "hystrix_consumer" + // HYSTRIX_PROVIDER ... HYSTRIX_PROVIDER = "hystrix_provider" - HYSTRIX = "hystrix" + // HYSTRIX ... + HYSTRIX = "hystrix" ) var ( @@ -57,6 +62,7 @@ func init() { extension.SetFilter(HYSTRIX_PROVIDER, GetHystrixFilterProvider) } +// HystrixFilterError ... type HystrixFilterError struct { err error failByHystrix bool @@ -66,9 +72,12 @@ func (hfError *HystrixFilterError) Error() string { return hfError.err.Error() } +// FailByHystrix ... func (hfError *HystrixFilterError) FailByHystrix() bool { return hfError.failByHystrix } + +// NewHystrixFilterError ... func NewHystrixFilterError(err error, failByHystrix bool) error { return &HystrixFilterError{ err: err, @@ -76,14 +85,15 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } +// HystrixFilter ... type HystrixFilter struct { COrP bool //true for consumer res map[string][]*regexp.Regexp ifNewMap sync.Map } -func (hf *HystrixFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { - +// Invoke ... +func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { cmdName := fmt.Sprintf("%s&method=%s", invoker.GetUrl().Key(), invocation.MethodName()) // Do the configuration if the circuit breaker is created for the first time @@ -115,12 +125,12 @@ func (hf *HystrixFilter) Invoke(invoker protocol.Invoker, invocation protocol.In configLoadMutex.RUnlock() if err != nil { logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: ", cmdName, err) - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } logger.Infof("[Hystrix Filter]Using hystrix filter: %s", cmdName) var result protocol.Result _ = hystrix.Do(cmdName, func() error { - result = invoker.Invoke(invocation) + result = invoker.Invoke(ctx, invocation) err := result.Error() if err != nil { result.SetError(NewHystrixFilterError(err, false)) @@ -144,9 +154,12 @@ func (hf *HystrixFilter) Invoke(invoker protocol.Invoker, invocation protocol.In return result } -func (hf *HystrixFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (hf *HystrixFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } + +// GetHystrixFilterConsumer ... func GetHystrixFilterConsumer() filter.Filter { //When first called, load the config in consumerConfigOnce.Do(func() { @@ -157,6 +170,7 @@ func GetHystrixFilterConsumer() filter.Filter { return &HystrixFilter{COrP: true} } +// GetHystrixFilterProvider ... func GetHystrixFilterProvider() filter.Filter { providerConfigOnce.Do(func() { if err := initHystrixConfigProvider(); err != nil { @@ -215,6 +229,7 @@ func initHystrixConfigConsumer() error { } return nil } + func initHystrixConfigProvider() error { if config.GetProviderConfig().FilterConf == nil { return perrors.Errorf("no config for hystrix") @@ -241,6 +256,7 @@ func initHystrixConfigProvider() error { // return initHystrixConfig() //} +// CommandConfigWithError ... type CommandConfigWithError struct { Timeout int `yaml:"timeout"` MaxConcurrentRequests int `yaml:"max_concurrent_requests"` @@ -258,11 +274,14 @@ type CommandConfigWithError struct { //- ErrorPercentThreshold: it causes circuits to open once the rolling measure of errors exceeds this percent of requests //See hystrix doc +// HystrixFilterConfig ... type HystrixFilterConfig struct { Configs map[string]*CommandConfigWithError Default string Services map[string]ServiceHystrixConfig } + +// ServiceHystrixConfig ... type ServiceHystrixConfig struct { ServiceConfig string `yaml:"service_config"` Methods map[string]string diff --git a/filter/impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go similarity index 90% rename from filter/impl/hystrix_filter_test.go rename to filter/filter_impl/hystrix_filter_test.go index d3a5183ede25d8a325bb1c73020edddd2ffbc638..71fc097c8bf4752e0cb2b451b0da7e16480b0701 100644 --- a/filter/impl/hystrix_filter_test.go +++ b/filter/filter_impl/hystrix_filter_test.go @@ -14,17 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package impl +package filter_impl import ( + "context" "regexp" "testing" ) + import ( "github.com/afex/hystrix-go/hystrix" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) + import ( "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" @@ -125,9 +128,9 @@ type testMockSuccessInvoker struct { protocol.BaseInvoker } -func (iv *testMockSuccessInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (iv *testMockSuccessInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Rest: "Sucess", + Rest: "Success", Err: nil, } } @@ -136,7 +139,7 @@ type testMockFailInvoker struct { protocol.BaseInvoker } -func (iv *testMockFailInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (iv *testMockFailInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { return &protocol.RPCResult{ Err: errors.Errorf("exception"), } @@ -144,7 +147,7 @@ func (iv *testMockFailInvoker) Invoke(invocation protocol.Invocation) protocol.R func TestHystrixFilter_Invoke_Success(t *testing.T) { hf := &HystrixFilter{} - result := hf.Invoke(&testMockSuccessInvoker{}, &invocation.RPCInvocation{}) + result := hf.Invoke(context.Background(), &testMockSuccessInvoker{}, &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.NoError(t, result.Error()) assert.NotNil(t, result.Result()) @@ -152,7 +155,7 @@ func TestHystrixFilter_Invoke_Success(t *testing.T) { func TestHystrixFilter_Invoke_Fail(t *testing.T) { hf := &HystrixFilter{} - result := hf.Invoke(&testMockFailInvoker{}, &invocation.RPCInvocation{}) + result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.Error(t, result.Error()) } @@ -164,7 +167,7 @@ func TestHystricFilter_Invoke_CircuitBreak(t *testing.T) { resChan := make(chan protocol.Result, 50) for i := 0; i < 50; i++ { go func() { - result := hf.Invoke(&testMockFailInvoker{}, &invocation.RPCInvocation{}) + result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) resChan <- result }() } @@ -189,7 +192,7 @@ func TestHystricFilter_Invoke_CircuitBreak_Omit_Exception(t *testing.T) { resChan := make(chan protocol.Result, 50) for i := 0; i < 50; i++ { go func() { - result := hf.Invoke(&testMockFailInvoker{}, &invocation.RPCInvocation{}) + result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) resChan <- result }() } @@ -210,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.go b/filter/filter_impl/metrics_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..f4734172b74c8bbcdac5c9a9743acb4df5fcb6b5 --- /dev/null +++ b/filter/filter_impl/metrics_filter.go @@ -0,0 +1,93 @@ +/* + * 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_impl + +import ( + "context" + "time" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +const ( + metricFilterName = "metrics" +) + +var ( + metricFilterInstance filter.Filter +) + +// must initialized before using the filter and after loading configuration +func init() { + extension.SetFilter(metricFilterName, newMetricsFilter) +} + +// metricFilter will calculate the invocation's duration and the report to the reporters +// If you want to use this filter to collect the metrics, +// Adding this into your configuration file, like: +// filter: "metrics" +// metrics: +// reporter: +// - "your reporter" # here you should specify the reporter, for example 'prometheus' +// more info please take a look at dubbo-samples projects +type metricsFilter struct { + reporters []metrics.Reporter +} + +// Invoke collect the duration of invocation and then report the duration by using goroutine +func (p *metricsFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + start := time.Now() + res := invoker.Invoke(ctx, invocation) + end := time.Now() + duration := end.Sub(start) + go func() { + for _, reporter := range p.reporters { + reporter.Report(ctx, invoker, invocation, duration, res) + } + }() + return res +} + +// OnResponse do nothing and return the result +func (p *metricsFilter) OnResponse(ctx context.Context, res protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return res +} + +// newMetricsFilter the metricsFilter is singleton. +// it's lazy initialization +// make sure that the configuration had been loaded before invoking this method. +func newMetricsFilter() filter.Filter { + if metricFilterInstance == nil { + reporterNames := config.GetMetricConfig().Reporters + reporters := make([]metrics.Reporter, 0, len(reporterNames)) + for _, name := range reporterNames { + reporters = append(reporters, extension.GetMetricReporter(name)) + } + metricFilterInstance = &metricsFilter{ + reporters: reporters, + } + } + + return metricFilterInstance +} diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..709404a2af4f4df0dbf625dbbbd673e34975c0db --- /dev/null +++ b/filter/filter_impl/metrics_filter_test.go @@ -0,0 +1,84 @@ +/* + * 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_impl + +import ( + "context" + "sync" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestMetricsFilter_Invoke(t *testing.T) { + + // prepare the mock reporter + config.GetMetricConfig().Reporters = []string{"mock"} + mk := &mockReporter{} + extension.SetMetricReporter("mock", func() metrics.Reporter { + return mk + }) + + instance := extension.GetFilter(metricFilterName) + + 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) + + attach := make(map[string]string, 10) + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + + ctx := context.Background() + + mk.On("Report", ctx, invoker, inv).Return(true, nil) + + mk.wg.Add(1) + result := instance.Invoke(ctx, invoker, inv) + assert.NotNil(t, result) + mk.AssertNotCalled(t, "Report", 1) + // it will do nothing + result = instance.OnResponse(ctx, nil, invoker, inv) + assert.Nil(t, result) +} + +type mockReporter struct { + mock.Mock + wg sync.WaitGroup +} + +func (m *mockReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { + m.Called(ctx, invoker, invocation) + m.wg.Done() +} diff --git a/filter/impl/token_filter.go b/filter/filter_impl/token_filter.go similarity index 76% rename from filter/impl/token_filter.go rename to filter/filter_impl/token_filter.go index d10dff5b761d0fbe40ff3a14a93ee8962d000e02..4605416c40a616361868c313881ae784257e6742 100644 --- a/filter/impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -15,9 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package impl +package filter_impl import ( + "context" "strings" ) @@ -33,6 +34,7 @@ import ( ) const ( + // TOKEN ... TOKEN = "token" ) @@ -40,27 +42,31 @@ func init() { extension.SetFilter(TOKEN, GetTokenFilter) } +// TokenFilter ... type TokenFilter struct{} -func (tf *TokenFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { invokerTkn := invoker.GetUrl().GetParam(constant.TOKEN_KEY, "") if len(invokerTkn) > 0 { attachs := invocation.Attachments() remoteTkn, exist := attachs[constant.TOKEN_KEY] if exist && strings.EqualFold(invokerTkn, remoteTkn) { - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } return &protocol.RPCResult{Err: perrors.Errorf("Invalid token! Forbid invoke remote service %v method %s ", invoker, invocation.MethodName())} } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (tf *TokenFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } +// GetTokenFilter ... func GetTokenFilter() filter.Filter { return &TokenFilter{} } diff --git a/filter/impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go similarity index 77% rename from filter/impl/token_filter_test.go rename to filter/filter_impl/token_filter_test.go index 1473f274037699260725ff9ebb1b3d1377efb326..672082c729bc371a40573a66d13bc57a7024186b 100644 --- a/filter/impl/token_filter_test.go +++ b/filter/filter_impl/token_filter_test.go @@ -15,9 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package impl +package filter_impl import ( + "context" "net/url" "testing" ) @@ -41,8 +42,10 @@ func TestTokenFilter_Invoke(t *testing.T) { common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) attch := make(map[string]string, 0) attch[constant.TOKEN_KEY] = "ori_key" - result := filter.Invoke(protocol.NewBaseInvoker(*url), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), + protocol.NewBaseInvoker(*url), + invocation.NewRPCInvocation("MethodName", + []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } @@ -53,8 +56,7 @@ func TestTokenFilter_InvokeEmptyToken(t *testing.T) { url := common.URL{} attch := make(map[string]string, 0) attch[constant.TOKEN_KEY] = "ori_key" - result := filter.Invoke(protocol.NewBaseInvoker(url), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } @@ -66,8 +68,7 @@ func TestTokenFilter_InvokeEmptyAttach(t *testing.T) { common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) attch := make(map[string]string, 0) - result := filter.Invoke(protocol.NewBaseInvoker(*url), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } @@ -79,7 +80,7 @@ func TestTokenFilter_InvokeNotEqual(t *testing.T) { common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) attch := make(map[string]string, 0) attch[constant.TOKEN_KEY] = "err_key" - result := filter.Invoke(protocol.NewBaseInvoker(*url), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), + protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } diff --git a/filter/impl/tps/impl/tps_limit_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go similarity index 82% rename from filter/impl/tps/impl/tps_limit_fix_window_strategy.go rename to filter/filter_impl/tps/tps_limit_fix_window_strategy.go index 7290619d5551ebe29209e7cfa717bd66442ca2df..a9c2ac15a417ffa6ff8f5b8d78d5c6a94877db30 100644 --- a/filter/impl/tps/impl/tps_limit_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go @@ -15,28 +15,32 @@ * limitations under the License. */ -package impl +package tps import ( "sync/atomic" "time" ) + import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/filter/impl/tps" + "github.com/apache/dubbo-go/filter" ) const ( + // FixedWindowKey ... FixedWindowKey = "fixedWindow" ) func init() { - extension.SetTpsLimitStrategy(FixedWindowKey, NewFixedWindowTpsLimitStrategyImpl) - extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, NewFixedWindowTpsLimitStrategyImpl) + creator := &fixedWindowStrategyCreator{} + extension.SetTpsLimitStrategy(FixedWindowKey, creator) + extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, creator) } /** + * FixedWindowTpsLimitStrategyImpl * It's the same as default implementation in Java * It's not a thread-safe implementation. * It you want to use the thread-safe implementation, please use ThreadSafeFixedWindowTpsLimitStrategyImpl @@ -61,6 +65,7 @@ type FixedWindowTpsLimitStrategyImpl struct { timestamp int64 } +// IsAllowable ... func (impl *FixedWindowTpsLimitStrategyImpl) IsAllowable() bool { current := time.Now().UnixNano() @@ -75,10 +80,12 @@ func (impl *FixedWindowTpsLimitStrategyImpl) IsAllowable() bool { return atomic.AddInt32(&impl.count, 1) <= impl.rate } -func NewFixedWindowTpsLimitStrategyImpl(rate int, interval int) tps.TpsLimitStrategy { +type fixedWindowStrategyCreator struct{} + +func (creator *fixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { return &FixedWindowTpsLimitStrategyImpl{ rate: int32(rate), - interval: int64(interval * 1000), // convert to ns + interval: int64(interval) * int64(time.Millisecond), // convert to ns count: 0, timestamp: time.Now().UnixNano(), } diff --git a/filter/impl/tps/impl/tps_limit_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go similarity index 88% rename from filter/impl/tps/impl/tps_limit_fix_window_strategy_test.go rename to filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go index 55d0b14b75e69b44cf9ebe3a615e1a05c60d4b41..5eaf2f707dcc9dd6cf325988242623dd5161c1a8 100644 --- a/filter/impl/tps/impl/tps_limit_fix_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package tps import ( "testing" @@ -27,16 +27,17 @@ import ( ) func TestFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { - strategy := NewFixedWindowTpsLimitStrategyImpl(2, 60000) + creator := &fixedWindowStrategyCreator{} + strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - strategy = NewFixedWindowTpsLimitStrategyImpl(2, 2000) + strategy = creator.Create(2, 2000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - time.Sleep(time.Duration(2100 * 1000)) + time.Sleep(2100 * time.Millisecond) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) diff --git a/filter/impl/tps/impl/tps_limit_sliding_window_strategy.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go similarity index 85% rename from filter/impl/tps/impl/tps_limit_sliding_window_strategy.go rename to filter/filter_impl/tps/tps_limit_sliding_window_strategy.go index de98eb7528f541ed57b04309e2c9c74b8310cc64..a781cc7bfbf297d0b9cf84ca0aa9dcfbbef7e14b 100644 --- a/filter/impl/tps/impl/tps_limit_sliding_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package tps import ( "container/list" @@ -25,14 +25,15 @@ import ( import ( "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/filter/impl/tps" + "github.com/apache/dubbo-go/filter" ) func init() { - extension.SetTpsLimitStrategy("slidingWindow", NewSlidingWindowTpsLimitStrategyImpl) + extension.SetTpsLimitStrategy("slidingWindow", &slidingWindowStrategyCreator{}) } /** + * SlidingWindowTpsLimitStrategyImpl * it's thread-safe. * "UserProvider": * registry: "hangzhouzk" @@ -53,6 +54,7 @@ type SlidingWindowTpsLimitStrategyImpl struct { queue *list.List } +// IsAllowable ... func (impl *SlidingWindowTpsLimitStrategyImpl) IsAllowable() bool { impl.mutex.Lock() defer impl.mutex.Unlock() @@ -80,10 +82,12 @@ func (impl *SlidingWindowTpsLimitStrategyImpl) IsAllowable() bool { return false } -func NewSlidingWindowTpsLimitStrategyImpl(rate int, interval int) tps.TpsLimitStrategy { +type slidingWindowStrategyCreator struct{} + +func (creator *slidingWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { return &SlidingWindowTpsLimitStrategyImpl{ rate: rate, - interval: int64(interval * 1000), + interval: int64(interval) * int64(time.Millisecond), mutex: &sync.Mutex{}, queue: list.New(), } diff --git a/filter/impl/tps/impl/tps_limit_sliding_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go similarity index 86% rename from filter/impl/tps/impl/tps_limit_sliding_window_strategy_test.go rename to filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go index 1d0187fa201741a32f109abe51ce63b5568e4cc4..57342d1c443993c49c6124f0ef28dae5ebb203e8 100644 --- a/filter/impl/tps/impl/tps_limit_sliding_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package tps import ( "testing" @@ -27,18 +27,19 @@ import ( ) func TestSlidingWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { - strategy := NewSlidingWindowTpsLimitStrategyImpl(2, 60000) + creator := &slidingWindowStrategyCreator{} + strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - time.Sleep(time.Duration(2100 * 1000)) + time.Sleep(2100 * time.Millisecond) assert.False(t, strategy.IsAllowable()) - strategy = NewSlidingWindowTpsLimitStrategyImpl(2, 2000) + strategy = creator.Create(2, 2000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - time.Sleep(time.Duration(2100 * 1000)) + time.Sleep(2100 * time.Millisecond) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) diff --git a/filter/impl/tps/impl/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go similarity index 99% rename from filter/impl/tps/impl/tps_limit_strategy_mock.go rename to filter/filter_impl/tps/tps_limit_strategy_mock.go index a653fb287a2d89d8c6151889ca14b4b7b4832505..72c658fb9a5d48b6080900a4645d318dfd2b0c21 100644 --- a/filter/impl/tps/impl/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -18,7 +18,7 @@ // Source: tps_limit_strategy.go // Package filter is a generated GoMock package. -package impl +package tps import ( gomock "github.com/golang/mock/gomock" diff --git a/filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go similarity index 75% rename from filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy.go rename to filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go index 5f43e8c3bf6c1db268282a0fb5d9ecc55fb357df..16624836e6397df5adda3f2aa5a80966721a97fb 100644 --- a/filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package tps import ( "sync" @@ -23,14 +23,17 @@ import ( import ( "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/filter/impl/tps" + "github.com/apache/dubbo-go/filter" ) func init() { - extension.SetTpsLimitStrategy("threadSafeFixedWindow", NewThreadSafeFixedWindowTpsLimitStrategyImpl) + extension.SetTpsLimitStrategy("threadSafeFixedWindow", &threadSafeFixedWindowStrategyCreator{ + fixedWindowStrategyCreator: &fixedWindowStrategyCreator{}, + }) } /** + * ThreadSafeFixedWindowTpsLimitStrategyImpl * it's the thread-safe implementation. * Also, it's a thread-safe decorator of FixedWindowTpsLimitStrategyImpl * "UserProvider": @@ -50,14 +53,19 @@ type ThreadSafeFixedWindowTpsLimitStrategyImpl struct { fixedWindow *FixedWindowTpsLimitStrategyImpl } +// IsAllowable ... func (impl *ThreadSafeFixedWindowTpsLimitStrategyImpl) IsAllowable() bool { impl.mutex.Lock() defer impl.mutex.Unlock() return impl.fixedWindow.IsAllowable() } -func NewThreadSafeFixedWindowTpsLimitStrategyImpl(rate int, interval int) tps.TpsLimitStrategy { - fixedWindowStrategy := NewFixedWindowTpsLimitStrategyImpl(rate, interval).(*FixedWindowTpsLimitStrategyImpl) +type threadSafeFixedWindowStrategyCreator struct { + fixedWindowStrategyCreator *fixedWindowStrategyCreator +} + +func (creator *threadSafeFixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { + fixedWindowStrategy := creator.fixedWindowStrategyCreator.Create(rate, interval).(*FixedWindowTpsLimitStrategyImpl) return &ThreadSafeFixedWindowTpsLimitStrategyImpl{ fixedWindow: fixedWindowStrategy, mutex: &sync.Mutex{}, diff --git a/filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go similarity index 87% rename from filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy_test.go rename to filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go index fea93dfa3b8ef49034e952619f617bf87e4be879..90cd15201cd71aafcc50a1dfb801ece7a5dee26a 100644 --- a/filter/impl/tps/impl/tps_limit_thread_safe_fix_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package tps import ( "testing" @@ -27,16 +27,17 @@ import ( ) func TestThreadSafeFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { - strategy := NewThreadSafeFixedWindowTpsLimitStrategyImpl(2, 60000) + creator := &threadSafeFixedWindowStrategyCreator{} + strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - strategy = NewThreadSafeFixedWindowTpsLimitStrategyImpl(2, 2000) + strategy = creator.Create(2, 2000) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) - time.Sleep(time.Duration(2100 * 1000)) + time.Sleep(2100 * time.Millisecond) assert.True(t, strategy.IsAllowable()) assert.True(t, strategy.IsAllowable()) assert.False(t, strategy.IsAllowable()) diff --git a/filter/impl/tps/impl/tps_limiter_method_service.go b/filter/filter_impl/tps/tps_limiter_method_service.go similarity index 94% rename from filter/impl/tps/impl/tps_limiter_method_service.go rename to filter/filter_impl/tps/tps_limiter_method_service.go index 3faf0d6e672315bb83e951be45a7f93c4ac508ef..7fe8de9237b82415a09083c2be59df5e232ecaf0 100644 --- a/filter/impl/tps/impl/tps_limiter_method_service.go +++ b/filter/filter_impl/tps/tps_limiter_method_service.go @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package impl + +package tps import ( "fmt" @@ -30,11 +31,13 @@ import ( "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/filter/impl/tps" + "github.com/apache/dubbo-go/filter" "github.com/apache/dubbo-go/protocol" ) -const name = "method-service" +const ( + name = "method-service" +) func init() { extension.SetTpsLimiter(constant.DEFAULT_KEY, GetMethodServiceTpsLimiter) @@ -42,6 +45,7 @@ func init() { } /** + * MethodServiceTpsLimiterImpl * This implementation allows developer to config both method-level and service-level tps limiter. * for example: * "UserProvider": @@ -111,6 +115,7 @@ type MethodServiceTpsLimiterImpl struct { tpsState *concurrent.Map } +// IsAllowable ... func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocation protocol.Invocation) bool { methodConfigPrefix := "methods." + invocation.MethodName() + "." @@ -127,7 +132,7 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio limitState, found := limiter.tpsState.Load(limitTarget) if found { - return limitState.(tps.TpsLimitStrategy).IsAllowable() + return limitState.(filter.TpsLimitStrategy).IsAllowable() } limitRate := getLimitConfig(methodLimitRateConfig, url, invocation, @@ -148,8 +153,8 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio limitStrategyConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_STRATEGY_KEY, url.GetParam(constant.TPS_LIMIT_STRATEGY_KEY, constant.DEFAULT_KEY)) limitStateCreator := extension.GetTpsLimitStrategyCreator(limitStrategyConfig) - limitState, _ = limiter.tpsState.LoadOrStore(limitTarget, limitStateCreator(int(limitRate), int(limitInterval))) - return limitState.(tps.TpsLimitStrategy).IsAllowable() + limitState, _ = limiter.tpsState.LoadOrStore(limitTarget, limitStateCreator.Create(int(limitRate), int(limitInterval))) + return limitState.(filter.TpsLimitStrategy).IsAllowable() } func getLimitConfig(methodLevelConfig string, @@ -178,7 +183,8 @@ func getLimitConfig(methodLevelConfig string, var methodServiceTpsLimiterInstance *MethodServiceTpsLimiterImpl var methodServiceTpsLimiterOnce sync.Once -func GetMethodServiceTpsLimiter() tps.TpsLimiter { +// GetMethodServiceTpsLimiter ... +func GetMethodServiceTpsLimiter() filter.TpsLimiter { methodServiceTpsLimiterOnce.Do(func() { methodServiceTpsLimiterInstance = &MethodServiceTpsLimiterImpl{ tpsState: concurrent.NewMap(), diff --git a/filter/impl/tps/impl/tps_limiter_method_service_test.go b/filter/filter_impl/tps/tps_limiter_method_service_test.go similarity index 83% rename from filter/impl/tps/impl/tps_limiter_method_service_test.go rename to filter/filter_impl/tps/tps_limiter_method_service_test.go index 006e9463871061488f696366d251c54fb8cefef5..441224a3e35147b85c3553871dcaa1fefd09db04 100644 --- a/filter/impl/tps/impl/tps_limiter_method_service_test.go +++ b/filter/filter_impl/tps/tps_limiter_method_service_test.go @@ -15,13 +15,14 @@ * limitations under the License. */ -package impl +package tps import ( "net/url" "testing" ) import ( + "github.com/apache/dubbo-go/filter" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -30,7 +31,6 @@ import ( "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/filter/impl/tps" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -48,10 +48,12 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Only_Service_Level(t *testing.T mockStrategyImpl := NewMockTpsLimitStrategy(ctrl) mockStrategyImpl.EXPECT().IsAllowable().Return(true).Times(1) - extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, func(rate int, interval int) tps.TpsLimitStrategy { - assert.Equal(t, 20, rate) - assert.Equal(t, 60000, interval) - return mockStrategyImpl + + extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, &mockStrategyCreator{ + rate: 20, + interval: 60000, + t: t, + strategy: mockStrategyImpl, }) limiter := GetMethodServiceTpsLimiter() @@ -95,10 +97,12 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Method_Level_Override(t *testin mockStrategyImpl := NewMockTpsLimitStrategy(ctrl) mockStrategyImpl.EXPECT().IsAllowable().Return(true).Times(1) - extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, func(rate int, interval int) tps.TpsLimitStrategy { - assert.Equal(t, 40, rate) - assert.Equal(t, 7000, interval) - return mockStrategyImpl + + extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, &mockStrategyCreator{ + rate: 40, + interval: 7000, + t: t, + strategy: mockStrategyImpl, }) limiter := GetMethodServiceTpsLimiter() @@ -123,13 +127,28 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Both_Method_And_Service(t *test mockStrategyImpl := NewMockTpsLimitStrategy(ctrl) mockStrategyImpl.EXPECT().IsAllowable().Return(true).Times(1) - extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, func(rate int, interval int) tps.TpsLimitStrategy { - assert.Equal(t, 40, rate) - assert.Equal(t, 3000, interval) - return mockStrategyImpl + + extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, &mockStrategyCreator{ + rate: 40, + interval: 3000, + t: t, + strategy: mockStrategyImpl, }) limiter := GetMethodServiceTpsLimiter() result := limiter.IsAllowable(*invokeUrl, invoc) assert.True(t, result) } + +type mockStrategyCreator struct { + rate int + interval int + t *testing.T + strategy filter.TpsLimitStrategy +} + +func (creator *mockStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { + assert.Equal(creator.t, creator.rate, rate) + assert.Equal(creator.t, creator.interval, interval) + return creator.strategy +} diff --git a/filter/impl/tps/impl/tps_limiter_mock.go b/filter/filter_impl/tps/tps_limiter_mock.go similarity index 98% rename from filter/impl/tps/impl/tps_limiter_mock.go rename to filter/filter_impl/tps/tps_limiter_mock.go index ff2f984e13a8617aefdbef0137ed8feca1bfd4ba..463b0988acbeb17a967c9803337a61c4914bce42 100644 --- a/filter/impl/tps/impl/tps_limiter_mock.go +++ b/filter/filter_impl/tps/tps_limiter_mock.go @@ -18,13 +18,19 @@ // Source: tps_limiter.go // Package filter is a generated GoMock package. -package impl +package tps + +import ( + reflect "reflect" +) + +import ( + gomock "github.com/golang/mock/gomock" +) import ( common "github.com/apache/dubbo-go/common" protocol "github.com/apache/dubbo-go/protocol" - gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockTpsLimiter is a mock of TpsLimiter interface diff --git a/filter/impl/tps_limit_filter.go b/filter/filter_impl/tps_limit_filter.go similarity index 80% rename from filter/impl/tps_limit_filter.go rename to filter/filter_impl/tps_limit_filter.go index 3cb7381c8616abd61fe2ac306b59694a92715dda..fa78288f9678d67d0eb0d025a83b75493f7fda80 100644 --- a/filter/impl/tps_limit_filter.go +++ b/filter/filter_impl/tps_limit_filter.go @@ -15,19 +15,23 @@ * limitations under the License. */ -package impl +package filter_impl +import ( + "context" +) import ( "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/filter" - _ "github.com/apache/dubbo-go/filter/common/impl" - _ "github.com/apache/dubbo-go/filter/impl/tps/impl" + _ "github.com/apache/dubbo-go/filter/filter_impl/tps" + _ "github.com/apache/dubbo-go/filter/handler" "github.com/apache/dubbo-go/protocol" ) const ( + // TpsLimitFilterKey key TpsLimitFilterKey = "tps" ) @@ -36,6 +40,7 @@ func init() { } /** + * TpsLimitFilter * if you wish to use the TpsLimiter, please add the configuration into your service provider configuration: * for example: * "UserProvider": @@ -51,25 +56,29 @@ func init() { type TpsLimitFilter struct { } -func (t TpsLimitFilter) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (t TpsLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { url := invoker.GetUrl() tpsLimiter := url.GetParam(constant.TPS_LIMITER_KEY, "") rejectedExeHandler := url.GetParam(constant.TPS_REJECTED_EXECUTION_HANDLER_KEY, constant.DEFAULT_KEY) if len(tpsLimiter) > 0 { allow := extension.GetTpsLimiter(tpsLimiter).IsAllowable(invoker.GetUrl(), invocation) if allow { - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } logger.Errorf("The invocation was rejected due to over the tps limitation, url: %s ", url.String()) return extension.GetRejectedExecutionHandler(rejectedExeHandler).RejectedExecution(url, invocation) } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (t TpsLimitFilter) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +// OnResponse ... +func (t TpsLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, + _ protocol.Invocation) protocol.Result { return result } +// GetTpsLimitFilter ... func GetTpsLimitFilter() filter.Filter { return &TpsLimitFilter{} } diff --git a/filter/impl/tps_limit_filter_test.go b/filter/filter_impl/tps_limit_filter_test.go similarity index 73% rename from filter/impl/tps_limit_filter_test.go rename to filter/filter_impl/tps_limit_filter_test.go index debdbd00dec97ed67d789bfc45103993c014ab4a..cc423ae1e5f3589dd60b0c8655f1123c290f0ffc 100644 --- a/filter/impl/tps_limit_filter_test.go +++ b/filter/filter_impl/tps_limit_filter_test.go @@ -15,14 +15,18 @@ * limitations under the License. */ -package impl +package filter_impl import ( + "context" "net/url" "testing" ) import ( + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/filter/filter_impl/tps" + common2 "github.com/apache/dubbo-go/filter/handler" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -31,10 +35,6 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" - filterCommon "github.com/apache/dubbo-go/filter/common" - filterCommonImpl "github.com/apache/dubbo-go/filter/common/impl" - "github.com/apache/dubbo-go/filter/impl/tps" - "github.com/apache/dubbo-go/filter/impl/tps/impl" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -46,8 +46,10 @@ func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) { common.WithParamsValue(constant.TPS_LIMITER_KEY, "")) attch := make(map[string]string, 0) - result := tpsFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := tpsFilter.Invoke(context.Background(), + protocol.NewBaseInvoker(*invokeUrl), + invocation.NewRPCInvocation("MethodName", + []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) @@ -56,9 +58,9 @@ func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) { func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockLimiter := impl.NewMockTpsLimiter(ctrl) + mockLimiter := tps.NewMockTpsLimiter(ctrl) mockLimiter.EXPECT().IsAllowable(gomock.Any(), gomock.Any()).Return(true).Times(1) - extension.SetTpsLimiter(constant.DEFAULT_KEY, func() tps.TpsLimiter { + extension.SetTpsLimiter(constant.DEFAULT_KEY, func() filter.TpsLimiter { return mockLimiter }) @@ -68,8 +70,10 @@ func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) { common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) attch := make(map[string]string, 0) - result := tpsFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := tpsFilter.Invoke(context.Background(), + protocol.NewBaseInvoker(*invokeUrl), + invocation.NewRPCInvocation("MethodName", + []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } @@ -77,17 +81,17 @@ func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) { func TestGenericFilter_Invoke_With_Default_TpsLimiter_Not_Allow(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockLimiter := impl.NewMockTpsLimiter(ctrl) + mockLimiter := tps.NewMockTpsLimiter(ctrl) mockLimiter.EXPECT().IsAllowable(gomock.Any(), gomock.Any()).Return(false).Times(1) - extension.SetTpsLimiter(constant.DEFAULT_KEY, func() tps.TpsLimiter { + extension.SetTpsLimiter(constant.DEFAULT_KEY, func() filter.TpsLimiter { return mockLimiter }) mockResult := &protocol.RPCResult{} - mockRejectedHandler := filterCommonImpl.NewMockRejectedExecutionHandler(ctrl) + mockRejectedHandler := common2.NewMockRejectedExecutionHandler(ctrl) mockRejectedHandler.EXPECT().RejectedExecution(gomock.Any(), gomock.Any()).Return(mockResult).Times(1) - extension.SetRejectedExecutionHandler(constant.DEFAULT_KEY, func() filterCommon.RejectedExecutionHandler { + extension.SetRejectedExecutionHandler(constant.DEFAULT_KEY, func() filter.RejectedExecutionHandler { return mockRejectedHandler }) @@ -97,8 +101,8 @@ func TestGenericFilter_Invoke_With_Default_TpsLimiter_Not_Allow(t *testing.T) { common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) attch := make(map[string]string, 0) - result := tpsFilter.Invoke(protocol.NewBaseInvoker(*invokeUrl), - invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := tpsFilter.Invoke(context.Background(), + protocol.NewBaseInvoker(*invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } diff --git a/filter/filter_impl/tracing_filter.go b/filter/filter_impl/tracing_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..b8058aa601af98b5416da882321546675459c413 --- /dev/null +++ b/filter/filter_impl/tracing_filter.go @@ -0,0 +1,107 @@ +/* + * 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_impl + +import ( + "context" +) + +import ( + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +const ( + tracingFilterName = "tracing" +) + +// this should be executed before users set their own Tracer +func init() { + extension.SetFilter(tracingFilterName, newTracingFilter) + opentracing.SetGlobalTracer(opentracing.NoopTracer{}) +} + +var ( + errorKey = "ErrorMsg" + successKey = "Success" +) + +// if you wish to using opentracing, please add the this filter into your filter attribute in your configure file. +// notice that this could be used in both client-side and server-side. +type tracingFilter struct { +} + +func (tf *tracingFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + var ( + spanCtx context.Context + span opentracing.Span + ) + operationName := invoker.GetUrl().ServiceKey() + "#" + invocation.MethodName() + + wiredCtx := ctx.Value(constant.TRACING_REMOTE_SPAN_CTX) + preSpan := opentracing.SpanFromContext(ctx) + + if preSpan != nil { + // it means that someone already create a span to trace, so we use the span to be the parent span + span = opentracing.StartSpan(operationName, opentracing.ChildOf(preSpan.Context())) + spanCtx = opentracing.ContextWithSpan(ctx, span) + + } else if wiredCtx != nil { + + // it means that there has a remote span, usually from client side. so we use this as the parent + span = opentracing.StartSpan(operationName, opentracing.ChildOf(wiredCtx.(opentracing.SpanContext))) + spanCtx = opentracing.ContextWithSpan(ctx, span) + } else { + // it means that there is not any span, so we create a span as the root span. + span, spanCtx = opentracing.StartSpanFromContext(ctx, operationName) + } + + defer func() { + span.Finish() + }() + + result := invoker.Invoke(spanCtx, invocation) + span.SetTag(successKey, result.Error() != nil) + if result.Error() != nil { + span.LogFields(log.String(errorKey, result.Error().Error())) + } + return result +} + +func (tf *tracingFilter) OnResponse(ctx context.Context, result protocol.Result, + invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return result +} + +var ( + tracingFilterInstance *tracingFilter +) + +func newTracingFilter() filter.Filter { + if tracingFilterInstance == nil { + tracingFilterInstance = &tracingFilter{} + } + return tracingFilterInstance +} diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a51692dddcc3400032650f4953eb1e28fb047709 --- /dev/null +++ b/filter/filter_impl/tracing_filter_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 filter_impl + +import ( + "context" + "testing" +) + +import ( + "github.com/opentracing/opentracing-go" +) + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestTracingFilter_Invoke(t *testing.T) { + 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) + + attach := make(map[string]string, 10) + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + ctx := context.Background() + tf := newTracingFilter() + + // do not has any span + tf.Invoke(ctx, invoker, inv) + + span, ctx := opentracing.StartSpanFromContext(ctx, "Test-Operation") + defer span.Finish() + + // has previous span + tf.Invoke(ctx, invoker, inv) + + // has remote ctx + ctx = context.WithValue(context.Background(), constant.TRACING_REMOTE_SPAN_CTX, span.Context()) + tf.Invoke(ctx, invoker, inv) +} diff --git a/filter/common/impl/rejected_execution_handler_mock.go b/filter/handler/rejected_execution_handler_mock.go similarity index 98% rename from filter/common/impl/rejected_execution_handler_mock.go rename to filter/handler/rejected_execution_handler_mock.go index 2f7869d61ea2cf4cf8e490dd004ab086b9492132..a5bef63b3729a7b04d911c9844320aa778ac357a 100644 --- a/filter/common/impl/rejected_execution_handler_mock.go +++ b/filter/handler/rejected_execution_handler_mock.go @@ -18,13 +18,19 @@ // Source: rejected_execution_handler.go // Package filter is a generated GoMock package. -package impl +package handler + +import ( + reflect "reflect" +) + +import ( + gomock "github.com/golang/mock/gomock" +) import ( common "github.com/apache/dubbo-go/common" protocol "github.com/apache/dubbo-go/protocol" - gomock "github.com/golang/mock/gomock" - reflect "reflect" ) // MockRejectedExecutionHandler is a mock of RejectedExecutionHandler interface diff --git a/filter/common/impl/rejected_execution_handler_only_log.go b/filter/handler/rejected_execution_handler_only_log.go similarity index 85% rename from filter/common/impl/rejected_execution_handler_only_log.go rename to filter/handler/rejected_execution_handler_only_log.go index 8943433af1ba908fc834740163df78e3b2b6443a..0f9003c7df2165a2f3a364a5afc47f578db1d243 100644 --- a/filter/common/impl/rejected_execution_handler_only_log.go +++ b/filter/handler/rejected_execution_handler_only_log.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package impl +package handler import ( + "github.com/apache/dubbo-go/filter" "sync" ) @@ -26,11 +27,13 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" - filterCommon "github.com/apache/dubbo-go/filter/common" "github.com/apache/dubbo-go/protocol" ) -const HandlerName = "log" +const ( + // HandlerName handler name + HandlerName = "log" +) func init() { extension.SetRejectedExecutionHandler(HandlerName, GetOnlyLogRejectedExecutionHandler) @@ -41,6 +44,7 @@ var onlyLogHandlerInstance *OnlyLogRejectedExecutionHandler var onlyLogHandlerOnce sync.Once /** + * OnlyLogRejectedExecutionHandler * This implementation only logs the invocation info. * it always return en error inside the result. * "UserProvider": @@ -56,12 +60,16 @@ var onlyLogHandlerOnce sync.Once type OnlyLogRejectedExecutionHandler struct { } -func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result { +// RejectedExecution ... +func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL, + _ protocol.Invocation) protocol.Result { + logger.Errorf("The invocation was rejected. url: %s", url.String()) return &protocol.RPCResult{} } -func GetOnlyLogRejectedExecutionHandler() filterCommon.RejectedExecutionHandler { +// GetOnlyLogRejectedExecutionHandler ... +func GetOnlyLogRejectedExecutionHandler() filter.RejectedExecutionHandler { onlyLogHandlerOnce.Do(func() { onlyLogHandlerInstance = &OnlyLogRejectedExecutionHandler{} }) diff --git a/filter/common/impl/rejected_execution_handler_only_log_test.go b/filter/handler/rejected_execution_handler_only_log_test.go similarity index 98% rename from filter/common/impl/rejected_execution_handler_only_log_test.go rename to filter/handler/rejected_execution_handler_only_log_test.go index da54d8a106338dd4f21f9b01e66b031e3c311e01..409f09f61bd958992749231fca045b54601fc627 100644 --- a/filter/common/impl/rejected_execution_handler_only_log_test.go +++ b/filter/handler/rejected_execution_handler_only_log_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package impl +package handler import ( "net/url" diff --git a/filter/common/rejected_execution_handler.go b/filter/rejected_execution_handler.go similarity index 97% rename from filter/common/rejected_execution_handler.go rename to filter/rejected_execution_handler.go index b993b8444c14c13ce9a8861c113dc02ca5fd335a..caeea1db6631d0968fd58f59f9577ee9272f3ca0 100644 --- a/filter/common/rejected_execution_handler.go +++ b/filter/rejected_execution_handler.go @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package common + +package filter import ( "github.com/apache/dubbo-go/common" @@ -22,6 +23,7 @@ import ( ) /** + * RejectedExecutionHandler * If the invocation cannot pass any validation in filter, like ExecuteLimitFilter and TpsLimitFilter, * the implementation will be used. * The common case is that sometimes you want to return the default value when the request was rejected. diff --git a/filter/impl/tps/tps_limit_strategy.go b/filter/tps_limit_strategy.go similarity index 89% rename from filter/impl/tps/tps_limit_strategy.go rename to filter/tps_limit_strategy.go index d1af85b464ca3cbb500100b895cdc0badff24898..5edf32ce1912642c7ad0ea0b3f6144b45c267eb4 100644 --- a/filter/impl/tps/tps_limit_strategy.go +++ b/filter/tps_limit_strategy.go @@ -15,9 +15,10 @@ * limitations under the License. */ -package tps +package filter /* + * TpsLimitStrategy * please register your implementation by invoking SetTpsLimitStrategy * "UserProvider": * registry: "hangzhouzk" @@ -34,3 +35,8 @@ package tps type TpsLimitStrategy interface { IsAllowable() bool } + +// TpsLimitStrategyCreator ... +type TpsLimitStrategyCreator interface { + Create(rate int, interval int) TpsLimitStrategy +} diff --git a/filter/impl/tps/tps_limiter.go b/filter/tps_limiter.go similarity index 97% rename from filter/impl/tps/tps_limiter.go rename to filter/tps_limiter.go index 0622a957a8ba14fdebb52ff44ecf72da17703163..dbc9f76838a4406b4788e7757453098613253d58 100644 --- a/filter/impl/tps/tps_limiter.go +++ b/filter/tps_limiter.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package tps +package filter import ( "github.com/apache/dubbo-go/common" @@ -23,6 +23,7 @@ import ( ) /* + * TpsLimiter * please register your implementation by invoking SetTpsLimiter * The usage, for example: * "UserProvider": diff --git a/go.mod b/go.mod index fb972ebd038b9272fac94518f382d42e57afbb16..b977df68c3df370280dd47f59d6f4577d01b908c 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.2.5-0.20191029001541-894e45c9aaaa + 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,12 +12,14 @@ 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.1 - github.com/dubbogo/gost v1.3.0 + 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 github.com/go-errors/errors v1.0.1 // indirect github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 + github.com/golang/protobuf v1.3.2 github.com/google/btree v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect @@ -31,19 +33,21 @@ require ( github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/magiconair/properties v1.8.1 + github.com/mitchellh/mapstructure v1.1.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb + github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.8.1 - github.com/prometheus/client_golang v1.1.0 // indirect - github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec + github.com/prometheus/client_golang v1.1.0 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 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.13+incompatible go.uber.org/atomic v1.4.0 diff --git a/go.sum b/go.sum index 783d51232a67ee20aa91f2f842924386eceeb0b5..eb775ec08af6b0e9e80b2f4782ca9432a8b1ecd8 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,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.2.5-0.20191029001541-894e45c9aaaa h1:11TO1wiM5bvGAVrmfN5atD8gZqUSPE1TBoIs8sI6Abk= -github.com/apache/dubbo-go-hessian2 v1.2.5-0.20191029001541-894e45c9aaaa/go.mod h1:LWnndnrFXZmJLAzoyNAPNHSIJ1KOHVkTSsHgC3YYWlo= +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= @@ -106,12 +106,14 @@ 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.1 h1:9fehwTo/D6+z6/+kADMbhbKeMkP80o/3g+XwV5lFLTY= -github.com/dubbogo/getty v1.3.1/go.mod h1:dtLOEb1v6EMHsQNYRWEACiRLmTWB2kJGUAj1aXayPOg= -github.com/dubbogo/gost v1.1.1 h1:JCM7vx5edPIjDA5ovJTuzEEXuw2t7xLyrlgi2mi5jHI= -github.com/dubbogo/gost v1.1.1/go.mod h1:R7wZm1DrmrKGr50mBZVcg6C9ekG8aL5hP+sgWcIDwQg= -github.com/dubbogo/gost v1.3.0 h1:n90mIUWCPD69BqW8wJ43NDy0RgNxx02aAG4QJcJ785U= -github.com/dubbogo/gost v1.3.0/go.mod h1:R7wZm1DrmrKGr50mBZVcg6C9ekG8aL5hP+sgWcIDwQg= +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= +github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= +github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= @@ -388,6 +390,8 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= @@ -430,8 +434,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= -github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -468,9 +470,13 @@ 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= +github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0= +github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= @@ -481,6 +487,8 @@ github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= +github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= @@ -507,7 +515,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.go b/metrics/prometheus/reporter.go new file mode 100644 index 0000000000000000000000000000000000000000..1636b14da2fe5ab714853aa662eaa774ddbc1791 --- /dev/null +++ b/metrics/prometheus/reporter.go @@ -0,0 +1,184 @@ +/* + * 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 prometheus + +import ( + "context" + "strconv" + "strings" + "sync" + "time" +) +import ( + "github.com/prometheus/client_golang/prometheus" +) + +import ( + "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/config" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +const ( + reporterName = "prometheus" + serviceKey = constant.SERVICE_KEY + groupKey = constant.GROUP_KEY + versionKey = constant.VERSION_KEY + methodKey = constant.METHOD_KEY + timeoutKey = constant.TIMEOUT_KEY + + providerKey = "provider" + consumerKey = "consumer" + + // to identify the metric's type + histogramSuffix = "_histogram" + // to identify the metric's type + summarySuffix = "_summary" +) + +var ( + labelNames = []string{serviceKey, groupKey, versionKey, methodKey, timeoutKey} + namespace = config.GetApplicationConfig().Name + reporterInstance *PrometheusReporter + reporterInitOnce sync.Once +) + +// should initialize after loading configuration +func init() { + + extension.SetMetricReporter(reporterName, newPrometheusReporter) +} + +// PrometheusReporter +// it will collect the data for Prometheus +// if you want to use this, you should initialize your prometheus. +// https://prometheus.io/docs/guides/go-application/ +type PrometheusReporter struct { + + // report the consumer-side's summary data + consumerSummaryVec *prometheus.SummaryVec + // report the provider-side's summary data + providerSummaryVec *prometheus.SummaryVec + + // report the provider-side's histogram data + providerHistogramVec *prometheus.HistogramVec + // report the consumer-side's histogram data + consumerHistogramVec *prometheus.HistogramVec +} + +// Report report the duration to Prometheus +// the role in url must be consumer or provider +// or it will be ignored +func (reporter *PrometheusReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { + url := invoker.GetUrl() + var sumVec *prometheus.SummaryVec + var hisVec *prometheus.HistogramVec + if isProvider(url) { + sumVec = reporter.providerSummaryVec + hisVec = reporter.providerHistogramVec + } else if isConsumer(url) { + sumVec = reporter.consumerSummaryVec + hisVec = reporter.consumerHistogramVec + } else { + logger.Warnf("The url is not the consumer's or provider's, "+ + "so the invocation will be ignored. url: %s", url.String()) + return + } + + labels := prometheus.Labels{ + serviceKey: url.Service(), + groupKey: url.GetParam(groupKey, ""), + versionKey: url.GetParam(versionKey, ""), + methodKey: invocation.MethodName(), + timeoutKey: url.GetParam(timeoutKey, ""), + } + + costMs := float64(cost.Nanoseconds() / constant.MsToNanoRate) + sumVec.With(labels).Observe(costMs) + hisVec.With(labels).Observe(costMs) +} + +func newHistogramVec(side string) *prometheus.HistogramVec { + mc := config.GetMetricConfig() + return prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: side, + Name: serviceKey + histogramSuffix, + Help: "This is the dubbo's histogram metrics", + Buckets: mc.GetHistogramBucket(), + }, + labelNames) +} + +// whether this url represents the application received the request as server +func isProvider(url common.URL) bool { + role := url.GetParam(constant.ROLE_KEY, "") + return strings.EqualFold(role, strconv.Itoa(common.PROVIDER)) +} + +// whether this url represents the application sent then request as client +func isConsumer(url common.URL) bool { + role := url.GetParam(constant.ROLE_KEY, "") + return strings.EqualFold(role, strconv.Itoa(common.CONSUMER)) +} + +// newSummaryVec create SummaryVec, the Namespace is dubbo +// the objectives is from my experience. +func newSummaryVec(side string) *prometheus.SummaryVec { + return prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Namespace: namespace, + Help: "This is the dubbo's summary metrics", + Subsystem: side, + Name: serviceKey + summarySuffix, + Objectives: map[float64]float64{ + 0.5: 0.01, + 0.75: 0.01, + 0.90: 0.005, + 0.98: 0.002, + 0.99: 0.001, + 0.999: 0.0001, + }, + }, + labelNames, + ) +} + +// newPrometheusReporter create new prometheusReporter +// it will register the metrics into prometheus +func newPrometheusReporter() metrics.Reporter { + if reporterInstance == nil { + reporterInitOnce.Do(func() { + reporterInstance = &PrometheusReporter{ + consumerSummaryVec: newSummaryVec(consumerKey), + providerSummaryVec: newSummaryVec(providerKey), + + consumerHistogramVec: newHistogramVec(consumerKey), + providerHistogramVec: newHistogramVec(providerKey), + } + prometheus.MustRegister(reporterInstance.consumerSummaryVec, reporterInstance.providerSummaryVec, + reporterInstance.consumerHistogramVec, reporterInstance.providerHistogramVec) + }) + } + return reporterInstance +} diff --git a/metrics/prometheus/reporter_test.go b/metrics/prometheus/reporter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0cb7d09a2c8e71fb88b54789c8eb3ee2cf967fbf --- /dev/null +++ b/metrics/prometheus/reporter_test.go @@ -0,0 +1,72 @@ +/* + * 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 prometheus + +import ( + "context" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestPrometheusReporter_Report(t *testing.T) { + reporter := extension.GetMetricReporter(reporterName) + 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) + + attach := make(map[string]string, 10) + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + + assert.False(t, isConsumer(url)) + ctx := context.Background() + reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) + + // consumer side + 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( + "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/metrics/reporter.go b/metrics/reporter.go new file mode 100644 index 0000000000000000000000000000000000000000..85ef1dcdf0dad275edecc1f3a85502c1493c1395 --- /dev/null +++ b/metrics/reporter.go @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package metrics + +import ( + "context" + "time" +) +import ( + "github.com/apache/dubbo-go/protocol" +) + +const ( + NameSpace = "dubbo" +) + +// it will be use to report the invocation's duration +type Reporter interface { + // report the duration of an invocation + Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, + cost time.Duration, res protocol.Result) +} diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index ba74d86c0c38ba02ec5e87423e0fe8990dae486b..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,8 +84,11 @@ func init() { return } setClientGrpool() + + rand.Seed(time.Now().UnixNano()) } +// SetClientConf ... func SetClientConf(c ClientConfig) { clientConf = &c err := clientConf.CheckValidity() @@ -95,6 +99,7 @@ func SetClientConf(c ClientConfig) { setClientGrpool() } +// GetClientConf ... func GetClientConf() ClientConfig { return *clientConf } @@ -106,6 +111,7 @@ func setClientGrpool() { } } +// Options ... type Options struct { // connect timeout ConnectTimeout time.Duration @@ -113,7 +119,9 @@ type Options struct { RequestTimeout time.Duration } -type CallResponse struct { +//AsyncCallbackResponse async response for dubbo +type AsyncCallbackResponse struct { + common.CallbackResponse Opts Options Cause error Start time.Time // invoke(call) start time == write start time @@ -121,8 +129,7 @@ type CallResponse struct { Reply interface{} } -type AsyncCallback func(response CallResponse) - +// Client ... type Client struct { opts Options conf ClientConfig @@ -132,14 +139,21 @@ type Client struct { pendingResponses *sync.Map } +// NewClient ... func NewClient(opt Options) *Client { switch { case opt.ConnectTimeout == 0: - opt.ConnectTimeout = 3e9 + opt.ConnectTimeout = 3 * time.Second fallthrough case opt.RequestTimeout == 0: - opt.RequestTimeout = 3e9 + 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{ @@ -147,11 +161,13 @@ func NewClient(opt Options) *Client { 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 } +// Request ... type Request struct { addr string svcUrl common.URL @@ -160,6 +176,7 @@ type Request struct { atta map[string]string } +// NewRequest ... func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request { return &Request{ addr: addr, @@ -170,11 +187,13 @@ func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, } } +// Response ... type Response struct { reply interface{} atta map[string]string } +// NewResponse ... func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, @@ -182,13 +201,13 @@ func NewResponse(reply interface{}, atta map[string]string) *Response { } } -// call one way +// CallOneway call one way func (c *Client) CallOneway(request *Request) error { return perrors.WithStack(c.call(CT_OneWay, request, NewResponse(nil, nil), nil)) } -// if @response is nil, the transport layer will get the response without notify the invoker. +// Call if @response is nil, the transport layer will get the response without notify the invoker. func (c *Client) Call(request *Request, response *Response) error { ct := CT_TwoWay @@ -199,12 +218,13 @@ func (c *Client) Call(request *Request, response *Response) error { return perrors.WithStack(c.call(ct, request, response, nil)) } -func (c *Client) AsyncCall(request *Request, callback AsyncCallback, response *Response) error { +// AsyncCall ... +func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error { return perrors.WithStack(c.call(CT_TwoWay, request, response, callback)) } -func (c *Client) call(ct CallType, request *Request, response *Response, callback AsyncCallback) error { +func (c *Client) call(ct CallType, request *Request, response *Response, callback common.AsyncCallback) error { p := &DubboPackage{} p.Service.Path = strings.TrimPrefix(request.svcUrl.Path, "/") @@ -212,7 +232,15 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac p.Service.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") p.Service.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") p.Service.Method = request.method + p.Service.Timeout = c.opts.RequestTimeout + var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") + if len(timeout) != 0 { + if t, err := time.ParseDuration(timeout); err == nil { + p.Service.Timeout = t + } + } + p.Header.SerialID = byte(S_Dubbo) p.Body = hessian.NewRequest(request.args, request.atta) @@ -238,7 +266,13 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac if session == nil { return errSessionNotExist } - defer c.pool.release(conn, err) + defer func() { + if err == nil { + c.pool.put(conn) + return + } + conn.close() + }() if err = c.transfer(session, p, rsp); err != nil { return perrors.WithStack(err) @@ -250,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 } @@ -259,6 +293,7 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac return perrors.WithStack(err) } +// Close ... func (c *Client) Close() { if c.pool != nil { c.pool.close() diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go index eb1f15c862a910120e118c06bf9b572e93f58832..1e0a73fac1a6cf6d4d102e5f4f6f1ba60fc4102a 100644 --- a/protocol/dubbo/client_test.go +++ b/protocol/dubbo/client_test.go @@ -144,8 +144,9 @@ func TestClient_AsyncCall(t *testing.T) { user := &User{} lock := sync.Mutex{} lock.Lock() - err := c.AsyncCall(NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), func(response CallResponse) { - assert.Equal(t, User{Id: "1", Name: "username"}, *response.Reply.(*Response).reply.(*User)) + err := c.AsyncCall(NewRequest("127.0.0.1:20000", url, "GetUser", []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) { + r := response.(AsyncCallbackResponse) + assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) lock.Unlock() }, NewResponse(user, nil)) assert.NoError(t, err) @@ -209,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 a878ffd91e29d6949870ec25fed9481f301b435a..76416b2baf1e1db516c00d92ecb8ad618bf186bd 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -26,31 +26,38 @@ import ( import ( "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go/common" perrors "github.com/pkg/errors" ) -// serial ID +//SerialID serial ID type SerialID byte const ( + // S_Dubbo dubbo serial id S_Dubbo SerialID = 2 ) -// call type +//CallType call type type CallType int32 const ( + // CT_UNKNOWN unknown call type CT_UNKNOWN CallType = 0 - CT_OneWay CallType = 1 - CT_TwoWay CallType = 2 + // CT_OneWay call one way + CT_OneWay CallType = 1 + // CT_TwoWay call in request/response + CT_TwoWay CallType = 2 ) //////////////////////////////////////////// // dubbo package //////////////////////////////////////////// +// SequenceType ... type SequenceType int64 +// DubboPackage ... type DubboPackage struct { Header hessian.DubboHeader Service hessian.Service @@ -62,6 +69,7 @@ func (p DubboPackage) String() string { return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) } +// Marshal ... func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { codec := hessian.NewHessianCodec(nil) @@ -73,8 +81,15 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } +// 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) @@ -88,11 +103,17 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { return perrors.Errorf("opts[0] is not of type *Client") } - pendingRsp, ok := client.pendingResponses.Load(SequenceType(p.Header.ID)) - if !ok { - return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) + if p.Header.Type&hessian.PackageRequest != 0x00 { + // size of this array must be '7' + // https://github.com/apache/dubbo-go-hessian2/blob/master/request.go#L272 + p.Body = make([]interface{}, 7) + } else { + pendingRsp, ok := client.pendingResponses.Load(SequenceType(p.Header.ID)) + if !ok { + return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) + } + p.Body = &hessian.Response{RspObj: pendingRsp.(*PendingResponse).response.reply} } - p.Body = &hessian.Response{RspObj: pendingRsp.(*PendingResponse).response.reply} } // read body @@ -104,16 +125,18 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // PendingResponse //////////////////////////////////////////// +// PendingResponse ... type PendingResponse struct { seq uint64 err error start time.Time readStart time.Time - callback AsyncCallback + callback common.AsyncCallback response *Response done chan struct{} } +// NewPendingResponse ... func NewPendingResponse() *PendingResponse { return &PendingResponse{ start: time.Now(), @@ -122,8 +145,9 @@ func NewPendingResponse() *PendingResponse { } } -func (r PendingResponse) GetCallResponse() CallResponse { - return CallResponse{ +// GetCallResponse ... +func (r PendingResponse) GetCallResponse() common.CallbackResponse { + return AsyncCallbackResponse{ Cause: r.err, Start: r.start, ReadStart: r.readStart, 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/config.go b/protocol/dubbo/config.go index 5371c587478ce7d064858108055c3f00f3ff4496..dbc6989c54780afacef717f1d110833d92967f9f 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -22,10 +22,12 @@ import ( ) import ( + "github.com/dubbogo/getty" perrors "github.com/pkg/errors" ) type ( + // GettySessionParam ... GettySessionParam struct { CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` @@ -45,7 +47,8 @@ type ( SessionName string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"` } - // Config holds supported types by the multiconfig package + // ServerConfig + //Config holds supported types by the multiconfig package ServerConfig struct { // session SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` @@ -61,7 +64,8 @@ type ( GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` } - // Config holds supported types by the multiconfig package + // ClientConfig + //Config holds supported types by the multiconfig package ClientConfig struct { ReconnectInterval int `default:"0" yaml:"reconnect_interval" json:"reconnect_interval,omitempty"` @@ -90,13 +94,14 @@ type ( } ) +// GetDefaultClientConfig ... func GetDefaultClientConfig() ClientConfig { return ClientConfig{ ReconnectInterval: 0, ConnectionNum: 16, - HeartbeatPeriod: "5s", - SessionTimeout: "20s", - PoolSize: 64, + HeartbeatPeriod: "30s", + SessionTimeout: "180s", + PoolSize: 4, PoolTTL: 600, GrPoolSize: 200, QueueLen: 64, @@ -105,7 +110,7 @@ func GetDefaultClientConfig() ClientConfig { CompressEncoding: false, TcpNoDelay: true, TcpKeepAlive: true, - KeepAlivePeriod: "120s", + KeepAlivePeriod: "180s", TcpRBufSize: 262144, TcpWBufSize: 65536, PkgWQSize: 512, @@ -117,9 +122,10 @@ func GetDefaultClientConfig() ClientConfig { }} } +// GetDefaultServerConfig ... func GetDefaultServerConfig() ServerConfig { return ServerConfig{ - SessionTimeout: "20s", + SessionTimeout: "180s", SessionNumber: 700, GrPoolSize: 120, QueueNumber: 6, @@ -128,7 +134,7 @@ func GetDefaultServerConfig() ServerConfig { CompressEncoding: false, TcpNoDelay: true, TcpKeepAlive: true, - KeepAlivePeriod: "120s", + KeepAlivePeriod: "180s", TcpRBufSize: 262144, TcpWBufSize: 65536, PkgWQSize: 512, @@ -141,6 +147,7 @@ func GetDefaultServerConfig() ServerConfig { } } +// CheckValidity ... func (c *GettySessionParam) CheckValidity() error { var err error @@ -163,6 +170,7 @@ func (c *GettySessionParam) CheckValidity() error { return nil } +// CheckValidity ... func (c *ClientConfig) CheckValidity() error { var err error @@ -172,6 +180,11 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) } + if c.heartbeatPeriod >= time.Duration(getty.MaxWheelTimeSpan) { + return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", + c.HeartbeatPeriod, time.Duration(getty.MaxWheelTimeSpan)) + } + if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } @@ -179,6 +192,7 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithStack(c.GettySessionParam.CheckValidity()) } +// CheckValidity ... func (c *ServerConfig) CheckValidity() error { var err error @@ -186,5 +200,10 @@ func (c *ServerConfig) CheckValidity() error { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } + if c.sessionTimeout >= time.Duration(getty.MaxWheelTimeSpan) { + return perrors.WithMessagef(err, "session_timeout %s should be less than %s", + c.SessionTimeout, time.Duration(getty.MaxWheelTimeSpan)) + } + return perrors.WithStack(c.GettySessionParam.CheckValidity()) } diff --git a/protocol/dubbo/dubbo_exporter.go b/protocol/dubbo/dubbo_exporter.go index cb06b6b69c9d0873342af5ea49fae054f029608c..f4cd0cc1234f71bdcf6ce746f01ff3618d820fc5 100644 --- a/protocol/dubbo/dubbo_exporter.go +++ b/protocol/dubbo/dubbo_exporter.go @@ -28,16 +28,19 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// DubboExporter ... type DubboExporter struct { protocol.BaseExporter } +// NewDubboExporter ... func NewDubboExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *DubboExporter { return &DubboExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } +// Unexport ... func (de *DubboExporter) Unexport() { serviceId := de.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") de.BaseExporter.Unexport() diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index bc321a97a4271c147d9317145d9f1aa76ca27902..09c3725710d2a0b821d8e641b0cb7b367189c244 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -18,11 +18,15 @@ package dubbo import ( + "context" "strconv" "sync" + "sync/atomic" + "time" ) import ( + "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) @@ -34,31 +38,49 @@ import ( invocation_impl "github.com/apache/dubbo-go/protocol/invocation" ) -var Err_No_Reply = perrors.New("request need @response") +var ( + // ErrNoReply ... + ErrNoReply = perrors.New("request need @response") + ErrDestroyedInvoker = perrors.New("request Destroyed invoker") +) var ( attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY} ) +// DubboInvoker ... type DubboInvoker struct { protocol.BaseInvoker - client *Client - destroyLock sync.Mutex + client *Client + quitOnce sync.Once + // Used to record the number of requests. -1 represent this DubboInvoker is destroyed + reqNum int64 } +// NewDubboInvoker ... func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, + reqNum: 0, } } -func (di *DubboInvoker) Invoke(invocation protocol.Invocation) protocol.Result { - +// Invoke ... +func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( 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 { @@ -66,6 +88,10 @@ func (di *DubboInvoker) Invoke(invocation protocol.Invocation) protocol.Result { inv.SetAttachments(k, v) } } + + // put the ctx into attachment + di.appendCtx(ctx, inv) + url := di.GetUrl() // async async, err := strconv.ParseBool(inv.AttachmentsByKey(constant.ASYNC_KEY, "false")) @@ -75,14 +101,14 @@ func (di *DubboInvoker) Invoke(invocation protocol.Invocation) protocol.Result { } response := NewResponse(inv.Reply(), nil) if async { - if callBack, ok := inv.CallBack().(func(response CallResponse)); ok { + if callBack, ok := inv.CallBack().(func(response common.CallbackResponse)); ok { result.Err = di.client.AsyncCall(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), callBack, response) } else { result.Err = di.client.CallOneway(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments())) } } else { if inv.Reply() == nil { - result.Err = Err_No_Reply + result.Err = ErrNoReply } else { result.Err = di.client.Call(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), response) } @@ -96,20 +122,37 @@ func (di *DubboInvoker) Invoke(invocation protocol.Invocation) protocol.Result { return &result } +// Destroy ... func (di *DubboInvoker) Destroy() { - if di.IsDestroyed() { - return - } - di.destroyLock.Lock() - defer di.destroyLock.Unlock() - - if di.IsDestroyed() { - return - } + di.quitOnce.Do(func() { + 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) + } - di.BaseInvoker.Destroy() + }) +} - if di.client != nil { - di.client.Close() // close client +// Finally, I made the decision that I don't provide a general way to transfer the whole context +// because it could be misused. If the context contains to many key-value pairs, the performance will be much lower. +func (di *DubboInvoker) appendCtx(ctx context.Context, inv *invocation_impl.RPCInvocation) { + // inject opentracing ctx + currentSpan := opentracing.SpanFromContext(ctx) + if currentSpan != nil { + carrier := opentracing.TextMapCarrier(inv.Attachments()) + err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + if err != nil { + logger.Errorf("Could not inject the span context into attachments: %v", err) + } } } diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go index 0a765356f7353829c8486fddba986e3a444441a1..1a64301f8200a4264001284cca1af3f0f1e07814 100644 --- a/protocol/dubbo/dubbo_invoker_test.go +++ b/protocol/dubbo/dubbo_invoker_test.go @@ -18,16 +18,19 @@ package dubbo import ( + "context" "sync" "testing" "time" ) import ( + "github.com/opentracing/opentracing-go" "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/invocation" ) @@ -39,8 +42,8 @@ func TestDubboInvoker_Invoke(t *testing.T) { pendingResponses: new(sync.Map), conf: *clientConf, opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, + ConnectTimeout: 3 * time.Second, + RequestTimeout: 6 * time.Second, }, } c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) @@ -52,32 +55,38 @@ func TestDubboInvoker_Invoke(t *testing.T) { invocation.WithReply(user), invocation.WithAttachments(map[string]string{"test_key": "test_value"})) // Call - res := invoker.Invoke(inv) + res := invoker.Invoke(context.Background(), inv) assert.NoError(t, res.Error()) assert.Equal(t, User{Id: "1", Name: "username"}, *res.Result().(*User)) assert.Equal(t, "test_value", res.Attachments()["test_key"]) // test attachments for request/response // CallOneway inv.SetAttachments(constant.ASYNC_KEY, "true") - res = invoker.Invoke(inv) + res = invoker.Invoke(context.Background(), inv) assert.NoError(t, res.Error()) // AsyncCall lock := sync.Mutex{} lock.Lock() - inv.SetCallBack(func(response CallResponse) { - assert.Equal(t, User{Id: "1", Name: "username"}, *response.Reply.(*Response).reply.(*User)) + inv.SetCallBack(func(response common.CallbackResponse) { + r := response.(AsyncCallbackResponse) + assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) lock.Unlock() }) - res = invoker.Invoke(inv) + res = invoker.Invoke(context.Background(), inv) assert.NoError(t, res.Error()) // Err_No_Reply inv.SetAttachments(constant.ASYNC_KEY, "false") inv.SetReply(nil) - res = invoker.Invoke(inv) + res = invoker.Invoke(context.Background(), inv) assert.EqualError(t, res.Error(), "request need @response") + // testing appendCtx + span, ctx := opentracing.StartSpanFromContext(context.Background(), "TestOperation") + invoker.Invoke(ctx, inv) + span.Finish() + // destroy lock.Lock() proto.Destroy() diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go index 59d1ea05160696754b46dfead5713684aa7a94f7..355dbc802488338ef4dbdd7290166038b312f183 100644 --- a/protocol/dubbo/dubbo_protocol.go +++ b/protocol/dubbo/dubbo_protocol.go @@ -19,17 +19,21 @@ package dubbo import ( "sync" + "time" ) import ( "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/config" "github.com/apache/dubbo-go/protocol" ) +// dubbo protocol constant const ( + // DUBBO ... DUBBO = "dubbo" ) @@ -41,12 +45,14 @@ var ( dubboProtocol *DubboProtocol ) +// DubboProtocol ... type DubboProtocol struct { protocol.BaseProtocol serverMap map[string]*Server serverLock sync.Mutex } +// NewDubboProtocol ... func NewDubboProtocol() *DubboProtocol { return &DubboProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -54,6 +60,7 @@ func NewDubboProtocol() *DubboProtocol { } } +// Export ... func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -66,16 +73,26 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } +// Refer ... func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker { + //default requestTimeout + var requestTimeout = config.GetConsumerConfig().RequestTimeout + + requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) + if t, err := time.ParseDuration(requestTimeoutStr); err == nil { + requestTimeout = t + } + invoker := NewDubboInvoker(url, NewClient(Options{ ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, - RequestTimeout: config.GetConsumerConfig().RequestTimeout, + RequestTimeout: requestTimeout, })) dp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) return invoker } +// Destroy ... func (dp *DubboProtocol) Destroy() { logger.Infof("DubboProtocol destroy.") @@ -107,6 +124,7 @@ func (dp *DubboProtocol) openServer(url common.URL) { } } +// GetProtocol ... func GetProtocol() protocol.Protocol { if dubboProtocol == nil { dubboProtocol = NewDubboProtocol() 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 df9ab28e0e4b896b11b2345a83cae14401a70759..0251b78a2b0d27a68461c16c284b1af53bcb08aa 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -18,15 +18,18 @@ package dubbo import ( + "context" "fmt" "net/url" "sync" + "sync/atomic" "time" ) import ( "github.com/apache/dubbo-go-hessian2" "github.com/dubbogo/getty" + "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) @@ -39,7 +42,10 @@ import ( ) // todo: WritePkg_Timeout will entry *.yml -const WritePkg_Timeout = 5 * time.Second +const ( + // WritePkg_Timeout ... + WritePkg_Timeout = 5 * time.Second +) var ( errTooManySessions = perrors.New("too many sessions") @@ -50,33 +56,47 @@ type rpcSession struct { reqNum int32 } -//////////////////////////////////////////// +func (s *rpcSession) AddReqNum(num int32) { + atomic.AddInt32(&s.reqNum, num) +} + +func (s *rpcSession) GetReqNum() int32 { + return atomic.LoadInt32(&s.reqNum) +} + +// ////////////////////////////////////////// // RpcClientHandler -//////////////////////////////////////////// +// ////////////////////////////////////////// +// RpcClientHandler ... type RpcClientHandler struct { conn *gettyRPCClient } +// NewRpcClientHandler ... func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { return &RpcClientHandler{conn: client} } +// OnOpen ... func (h *RpcClientHandler) OnOpen(session getty.Session) error { h.conn.addSession(session) return nil } +// OnError ... func (h *RpcClientHandler) OnError(session getty.Session, err error) { logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.conn.removeSession(session) } +// OnClose ... func (h *RpcClientHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.conn.removeSession(session) } +// OnMessage ... func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { p, ok := pkg.(*DubboPackage) if !ok { @@ -85,11 +105,17 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { } if p.Header.Type&hessian.PackageHeartbeat != 0x00 { - logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) - if p.Err != nil { - logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) + if p.Header.Type&hessian.PackageResponse != 0x00 { + logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) + if p.Err != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) + } + h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) + } else { + logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) + p.Header.ResponseStatus = hessian.Response_OK + reply(session, p, hessian.PackageHeartbeat) } - h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) return } logger.Debugf("get rpc response{header: %#v, body: %#v}", p.Header, p.Body) @@ -98,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 } @@ -114,6 +141,7 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { } } +// OnCron ... func (h *RpcClientHandler) OnCron(session getty.Session) { rpcSession, err := h.conn.getClientRpcSession(session) if err != nil { @@ -131,10 +159,11 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { h.conn.pool.rpcClient.heartbeat(session) } -//////////////////////////////////////////// +// ////////////////////////////////////////// // RpcServerHandler -//////////////////////////////////////////// +// ////////////////////////////////////////// +// RpcServerHandler ... type RpcServerHandler struct { maxSessionNum int sessionTimeout time.Duration @@ -142,6 +171,7 @@ type RpcServerHandler struct { rwlock sync.RWMutex } +// NewRpcServerHandler ... func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { return &RpcServerHandler{ maxSessionNum: maxSessionNum, @@ -150,6 +180,7 @@ func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcSe } } +// OnOpen ... func (h *RpcServerHandler) OnOpen(session getty.Session) error { var err error h.rwlock.RLock() @@ -168,6 +199,7 @@ func (h *RpcServerHandler) OnOpen(session getty.Session) error { return nil } +// OnError ... func (h *RpcServerHandler) OnError(session getty.Session, err error) { logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.rwlock.Lock() @@ -175,6 +207,7 @@ func (h *RpcServerHandler) OnError(session getty.Session, err error) { h.rwlock.Unlock() } +// OnClose ... func (h *RpcServerHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.rwlock.Lock() @@ -182,6 +215,7 @@ func (h *RpcServerHandler) OnClose(session getty.Session) { h.rwlock.Unlock() } +// OnMessage ... func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { h.rwlock.Lock() if _, ok := h.sessionMap[session]; ok { @@ -199,7 +233,7 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { // heartbeat if p.Header.Type&hessian.PackageHeartbeat != 0x00 { logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - h.reply(session, p, hessian.PackageHeartbeat) + reply(session, p, hessian.PackageHeartbeat) return } @@ -226,7 +260,7 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { if !twoway { return } - h.reply(session, p, hessian.PackageResponse) + reply(session, p, hessian.PackageResponse) } }() @@ -241,7 +275,7 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { logger.Errorf(err.Error()) p.Header.ResponseStatus = hessian.Response_OK p.Body = err - h.reply(session, p, hessian.PackageResponse) + reply(session, p, hessian.PackageResponse) return } invoker := exporter.(protocol.Exporter).GetInvoker() @@ -252,7 +286,10 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { args := p.Body.(map[string]interface{})["args"].([]interface{}) inv := invocation.NewRPCInvocation(p.Service.Method, args, attachments) - result := invoker.Invoke(inv) + + ctx := rebuildCtx(inv) + + result := invoker.Invoke(ctx, inv) if err := result.Error(); err != nil { p.Header.ResponseStatus = hessian.Response_OK p.Body = hessian.NewResponse(nil, err, result.Attachments()) @@ -266,9 +303,10 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { if !twoway { return } - h.reply(session, p, hessian.PackageResponse) + reply(session, p, hessian.PackageResponse) } +// OnCron ... func (h *RpcServerHandler) OnCron(session getty.Session) { var ( flag bool @@ -294,7 +332,22 @@ func (h *RpcServerHandler) OnCron(session getty.Session) { } } -func (h *RpcServerHandler) reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { +// rebuildCtx rebuild the context by attachment. +// Once we decided to transfer more context's key-value, we should change this. +// now we only support rebuild the tracing context +func rebuildCtx(inv *invocation.RPCInvocation) context.Context { + ctx := context.Background() + + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(inv.Attachments())) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} + +func reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { resp := &DubboPackage{ Header: hessian.DubboHeader{ SerialID: req.Header.SerialID, diff --git a/protocol/dubbo/listener_test.go b/protocol/dubbo/listener_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5f809814607558650e09934019db96dbb2ceeeae --- /dev/null +++ b/protocol/dubbo/listener_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 dubbo + +import ( + "testing" +) + +import ( + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/mocktracer" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol/invocation" +) + +// test rebuild the ctx +func TestRebuildCtx(t *testing.T) { + opentracing.SetGlobalTracer(mocktracer.New()) + attach := make(map[string]string, 10) + attach[constant.VERSION_KEY] = "1.0" + attach[constant.GROUP_KEY] = "MyGroup" + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + + // attachment doesn't contains any tracing key-value pair, + ctx := rebuildCtx(inv) + assert.NotNil(t, ctx) + assert.Nil(t, ctx.Value(constant.TRACING_REMOTE_SPAN_CTX)) + + span, ctx := opentracing.StartSpanFromContext(ctx, "Test-Client") + + opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, + opentracing.TextMapCarrier(inv.Attachments())) + // rebuild the context success + inv = invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + ctx = rebuildCtx(inv) + span.Finish() + assert.NotNil(t, ctx) + assert.NotNil(t, ctx.Value(constant.TRACING_REMOTE_SPAN_CTX)) +} diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index d619a2f8fe78524b3d704cb9de280ebbf534eb12..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 @@ -154,11 +154,11 @@ func (c *gettyRPCClient) addSession(session getty.Session) { } c.lock.Lock() + defer c.lock.Unlock() if c.sessions == nil { c.sessions = make([]*rpcSession, 0, 16) } c.sessions = append(c.sessions, &rpcSession{session: session}) - c.lock.Unlock() } func (c *gettyRPCClient) removeSession(session getty.Session) { @@ -166,21 +166,27 @@ func (c *gettyRPCClient) removeSession(session getty.Session) { return } - c.lock.Lock() - defer c.lock.Unlock() - if c.sessions == nil { - return - } + var removeFlag bool + func() { + c.lock.Lock() + defer c.lock.Unlock() + if c.sessions == nil { + return + } - for i, s := range c.sessions { - if s.session == session { - c.sessions = append(c.sessions[:i], c.sessions[i+1:]...) - logger.Debugf("delete session{%s}, its index{%d}", session.Stat(), i) - break + for i, s := range c.sessions { + if s.session == session { + c.sessions = append(c.sessions[:i], c.sessions[i+1:]...) + logger.Debugf("delete session{%s}, its index{%d}", session.Stat(), i) + break + } } - } - logger.Infof("after remove session{%s}, left session number:%d", session.Stat(), len(c.sessions)) - if len(c.sessions) == 0 { + logger.Infof("after remove session{%s}, left session number:%d", session.Stat(), len(c.sessions)) + if len(c.sessions) == 0 { + removeFlag = true + } + }() + if removeFlag { c.pool.safeRemove(c) c.close() } @@ -190,17 +196,24 @@ func (c *gettyRPCClient) updateSession(session getty.Session) { if session == nil { return } - c.lock.Lock() - defer c.lock.Unlock() - if c.sessions == nil { - return - } - for i, s := range c.sessions { - if s.session == session { - c.sessions[i].reqNum++ - break + var rs *rpcSession + func() { + c.lock.RLock() + defer c.lock.RUnlock() + if c.sessions == nil { + return + } + + for i, s := range c.sessions { + if s.session == session { + rs = c.sessions[i] + break + } } + }() + if rs != nil { + rs.AddReqNum(1) } } @@ -238,28 +251,42 @@ func (c *gettyRPCClient) isAvailable() bool { func (c *gettyRPCClient) close() error { closeErr := perrors.Errorf("close gettyRPCClient{%#v} again", c) c.once.Do(func() { - c.gettyClient.Close() - c.gettyClient = nil - for _, s := range c.sessions { - logger.Infof("close client session{%s, last active:%s, request number:%d}", - s.session.Stat(), s.session.GetActive().String(), s.reqNum) - s.session.Close() - } - c.sessions = c.sessions[:0] + var ( + gettyClient getty.Client + sessions []*rpcSession + ) + func() { + c.lock.Lock() + defer c.lock.Unlock() + + gettyClient = c.gettyClient + c.gettyClient = nil + + sessions = make([]*rpcSession, 0, len(c.sessions)) + for _, s := range c.sessions { + sessions = append(sessions, s) + } + c.sessions = c.sessions[:0] + }() c.updateActive(0) + + go func() { + if gettyClient != nil { + gettyClient.Close() + } + for _, s := range sessions { + logger.Infof("close client session{%s, last active:%s, request number:%d}", + s.session.Stat(), s.session.GetActive().String(), s.GetReqNum()) + s.session.Close() + } + }() + closeErr = nil }) return closeErr } -func (c *gettyRPCClient) safeClose() error { - c.lock.Lock() - defer c.lock.Unlock() - - return c.close() -} - type gettyRPCClientPool struct { rpcClient *Client size int // size of []*gettyRPCClient @@ -284,11 +311,22 @@ func (p *gettyRPCClientPool) close() { p.conns = nil p.Unlock() for _, conn := range conns { - conn.safeClose() + conn.close() } } func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPCClient, error) { + conn, err := p.get() + if err == nil && conn == nil { + // create new conn + rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr) + return rpcClientConn, perrors.WithStack(err) + } + return conn, perrors.WithStack(err) +} + +func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) { + now := time.Now().Unix() p.Lock() defer p.Unlock() @@ -296,35 +334,26 @@ func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPC return nil, errClientPoolClosed } - now := time.Now().Unix() - for len(p.conns) > 0 { conn := p.conns[len(p.conns)-1] p.conns = p.conns[:len(p.conns)-1] if d := now - conn.getActive(); d > p.ttl { p.remove(conn) - conn.safeClose() + go conn.close() continue } conn.updateActive(now) //update active time - return conn, nil } - // create new conn - return newGettyRPCClientConn(p, protocol, addr) + return nil, nil } -func (p *gettyRPCClientPool) release(conn *gettyRPCClient, err error) { +func (p *gettyRPCClientPool) put(conn *gettyRPCClient) { if conn == nil || conn.getActive() == 0 { return } - if err != nil { - conn.safeClose() - return - } - p.Lock() defer p.Unlock() @@ -332,10 +361,17 @@ func (p *gettyRPCClientPool) release(conn *gettyRPCClient, err error) { 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) - conn.safeClose() + // p.remove(conn) + conn.close() return } p.conns = append(p.conns, conn) diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index 930382cca8bac6955b516a88e93ce26d73e235fe..b5c4f509190dbdc85825ad424656240b234786df 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -38,10 +38,12 @@ import ( // RpcClientPackageHandler //////////////////////////////////////////// +// RpcClientPackageHandler ... type RpcClientPackageHandler struct { client *Client } +// NewRpcClientPackageHandler ... func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { return &RpcClientPackageHandler{client: client} } @@ -62,8 +64,10 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface return nil, 0, perrors.WithStack(err) } - pkg.Err = pkg.Body.(*hessian.Response).Exception - pkg.Body = NewResponse(pkg.Body.(*hessian.Response).RspObj, pkg.Body.(*hessian.Response).Attachments) + if pkg.Header.Type&hessian.PackageRequest == 0x00 { + pkg.Err = pkg.Body.(*hessian.Response).Exception + pkg.Body = NewResponse(pkg.Body.(*hessian.Response).RspObj, pkg.Body.(*hessian.Response).Attachments) + } return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil } @@ -92,6 +96,7 @@ var ( rpcServerPkgHandler = &RpcServerPackageHandler{} ) +// RpcServerPackageHandler ... type RpcServerPackageHandler struct{} func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index 648c9f8aa8d24a321bfda85279a6470c745dbfa1..bd2b37b7a9f055745e183524d19a442af03360f4 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -74,6 +74,7 @@ func init() { SetServerGrpool() } +// SetServerConfig ... func SetServerConfig(s ServerConfig) { srvConf = &s err := srvConf.CheckValidity() @@ -84,10 +85,12 @@ func SetServerConfig(s ServerConfig) { SetServerGrpool() } +// GetServerConfig ... func GetServerConfig() ServerConfig { return *srvConf } +// SetServerGrpool ... func SetServerGrpool() { if srvConf.GrPoolSize > 1 { srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen), @@ -95,12 +98,14 @@ func SetServerGrpool() { } } +// Server ... type Server struct { conf ServerConfig tcpServer getty.Server rpcHandler *RpcServerHandler } +// NewServer ... func NewServer() *Server { s := &Server{ @@ -151,6 +156,7 @@ func (s *Server) newSession(session getty.Session) error { return nil } +// Start ... func (s *Server) Start(url common.URL) { var ( addr string @@ -167,6 +173,7 @@ func (s *Server) Start(url common.URL) { } +// Stop ... func (s *Server) Stop() { s.tcpServer.Close() } diff --git a/protocol/grpc/client.go b/protocol/grpc/client.go new file mode 100644 index 0000000000000000000000000000000000000000..d35a2c770cd8b9bda805715889791ccf53c562db --- /dev/null +++ b/protocol/grpc/client.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 grpc + +import ( + "reflect" +) + +import ( + "google.golang.org/grpc" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/config" +) + +// Client ... +type Client struct { + *grpc.ClientConn + invoker reflect.Value +} + +// NewClient ... +func NewClient(url common.URL) *Client { + conn, err := grpc.Dial(url.Location, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + panic(err) + } + + key := url.GetParam(constant.BEAN_NAME_KEY, "") + impl := config.GetConsumerService(key) + invoker := getInvoker(impl, conn) + + return &Client{ + ClientConn: conn, + invoker: reflect.ValueOf(invoker), + } +} + +func getInvoker(impl interface{}, conn *grpc.ClientConn) interface{} { + in := []reflect.Value{} + in = append(in, reflect.ValueOf(conn)) + method := reflect.ValueOf(impl).MethodByName("GetDubboStub") + res := method.Call(in) + return res[0].Interface() +} diff --git a/protocol/grpc/client_test.go b/protocol/grpc/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..56ec766f70da93bcddbcff13667a34c39deffe06 --- /dev/null +++ b/protocol/grpc/client_test.go @@ -0,0 +1,53 @@ +/* +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 grpc + +import ( + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol/grpc/internal" +) + +func TestGetInvoker(t *testing.T) { + var conn *grpc.ClientConn + var impl *internal.GrpcGreeterImpl + invoker := getInvoker(impl, conn) + + i := reflect.TypeOf(invoker) + expected := reflect.TypeOf(internal.NewGreeterClient(nil)) + assert.Equal(t, i, expected) +} + +func TestNewClient(t *testing.T) { + go internal.InitGrpcServer() + defer internal.ShutdownGrpcServer() + + 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/common_test.go b/protocol/grpc/common_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3d0823b1061a61cfa391358e270c8b9081e9031c --- /dev/null +++ b/protocol/grpc/common_test.go @@ -0,0 +1,114 @@ +/* +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 grpc + +import ( + "context" + "fmt" +) + +import ( + native_grpc "google.golang.org/grpc" +) + +import ( + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/grpc/internal" + "github.com/apache/dubbo-go/protocol/invocation" +) + +// userd grpc-dubbo biz service +func addService() { + config.SetProviderService(newGreeterProvider()) +} + +type greeterProvider struct { + *greeterProviderBase +} + +func newGreeterProvider() *greeterProvider { + return &greeterProvider{ + greeterProviderBase: &greeterProviderBase{}, + } +} + +func (g *greeterProvider) SayHello(ctx context.Context, req *internal.HelloRequest) (reply *internal.HelloReply, err error) { + fmt.Printf("req: %v", req) + return &internal.HelloReply{Message: "this is message from reply"}, nil +} + +func (g *greeterProvider) Reference() string { + return "GrpcGreeterImpl" +} + +// code generated by greeter.go +type greeterProviderBase struct { + proxyImpl protocol.Invoker +} + +func (g *greeterProviderBase) SetProxyImpl(impl protocol.Invoker) { + g.proxyImpl = impl +} + +func (g *greeterProviderBase) GetProxyImpl() protocol.Invoker { + return g.proxyImpl +} + +func (g *greeterProviderBase) ServiceDesc() *native_grpc.ServiceDesc { + return &native_grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*internal.GreeterServer)(nil), + Methods: []native_grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: dubboGreeterSayHelloHandler, + }, + }, + Streams: []native_grpc.StreamDesc{}, + Metadata: "helloworld.proto", + } +} + +func dubboGreeterSayHelloHandler(srv interface{}, ctx context.Context, + dec func(interface{}) error, interceptor native_grpc.UnaryServerInterceptor) (interface{}, error) { + + in := new(internal.HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + base := srv.(DubboGrpcService) + + args := []interface{}{} + args = append(args, in) + invo := invocation.NewRPCInvocation("SayHello", args, nil) + + if interceptor == nil { + result := base.GetProxyImpl().Invoke(context.Background(), invo) + return result.Result(), result.Error() + } + info := &native_grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + result := base.GetProxyImpl().Invoke(context.Background(), invo) + return result.Result(), result.Error() + } + return interceptor(ctx, in, info, handler) +} diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go new file mode 100644 index 0000000000000000000000000000000000000000..3c38ef974ca22a582ce83102718d01a8edd4258f --- /dev/null +++ b/protocol/grpc/grpc_exporter.go @@ -0,0 +1,51 @@ +/* +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 grpc + +import ( + "sync" +) + +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/protocol" +) + +// GrpcExporter ... +type GrpcExporter struct { + *protocol.BaseExporter +} + +// NewGrpcExporter ... +func NewGrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *GrpcExporter { + return &GrpcExporter{ + BaseExporter: protocol.NewBaseExporter(key, invoker, exporterMap), + } +} + +// Unexport ... +func (gg *GrpcExporter) Unexport() { + serviceId := gg.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + gg.BaseExporter.Unexport() + err := common.ServiceMap.UnRegister(GRPC, serviceId) + if err != nil { + logger.Errorf("[GrpcExporter.Unexport] error: %v", err) + } +} diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go new file mode 100644 index 0000000000000000000000000000000000000000..26bc86f3aa46c8048b16284bd61cb5d9fb4664f9 --- /dev/null +++ b/protocol/grpc/grpc_invoker.go @@ -0,0 +1,104 @@ +/* +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 grpc + +import ( + "context" + "reflect" + "sync" +) + +import ( + "github.com/pkg/errors" + "google.golang.org/grpc/connectivity" +) + +import ( + hessian2 "github.com/apache/dubbo-go-hessian2" + + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +// ErrNoReply ... +var ErrNoReply = errors.New("request need @response") + +// GrpcInvoker ... +type GrpcInvoker struct { + protocol.BaseInvoker + quitOnce sync.Once + client *Client +} + +// NewGrpcInvoker ... +func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { + return &GrpcInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + client: client, + } +} + +// Invoke ... +func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + var ( + result protocol.RPCResult + ) + + if invocation.Reply() == nil { + result.Err = ErrNoReply + } + + in := []reflect.Value{} + in = append(in, reflect.ValueOf(context.Background())) + in = append(in, invocation.ParameterValues()...) + + methodName := invocation.MethodName() + method := gi.client.invoker.MethodByName(methodName) + res := method.Call(in) + + result.Rest = res[0] + // check err + if !res[1].IsNil() { + result.Err = res[1].Interface().(error) + } else { + _ = hessian2.ReflectResponse(res[0], invocation.Reply()) + } + + return &result +} + +// IsAvailable ... +func (gi *GrpcInvoker) IsAvailable() bool { + return gi.BaseInvoker.IsAvailable() && gi.client.GetState() != connectivity.Shutdown +} + +// IsDestroyed ... +func (gi *GrpcInvoker) IsDestroyed() bool { + return gi.BaseInvoker.IsDestroyed() && gi.client.GetState() == connectivity.Shutdown +} + +// Destroy ... +func (gi *GrpcInvoker) Destroy() { + gi.quitOnce.Do(func() { + gi.BaseInvoker.Destroy() + + if gi.client != nil { + _ = gi.client.Close() + } + }) +} diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..368c1392ec03af310f93e8fc2173b8354975c99e --- /dev/null +++ b/protocol/grpc/grpc_invoker_test.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 grpc + +import ( + "context" + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol/grpc/internal" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestInvoke(t *testing.T) { + go internal.InitGrpcServer() + defer internal.ShutdownGrpcServer() + + 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) + + invoker := NewGrpcInvoker(url, cli) + + args := []reflect.Value{} + args = append(args, reflect.ValueOf(&internal.HelloRequest{Name: "request name"})) + bizReply := &internal.HelloReply{} + invo := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("SayHello"), + invocation.WithParameterValues(args), invocation.WithReply(bizReply)) + res := invoker.Invoke(context.Background(), invo) + assert.Nil(t, res.Error()) + assert.NotNil(t, res.Result()) + assert.Equal(t, "Hello request name", bizReply.Message) +} diff --git a/protocol/grpc/grpc_protocol.go b/protocol/grpc/grpc_protocol.go new file mode 100644 index 0000000000000000000000000000000000000000..0f5625c152cc366289143b8a29d11cafb513b2f2 --- /dev/null +++ b/protocol/grpc/grpc_protocol.go @@ -0,0 +1,113 @@ +/* +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 grpc + +import ( + "sync" +) + +import ( + "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" +) + +const ( + // GRPC module name + GRPC = "grpc" +) + +func init() { + extension.SetProtocol(GRPC, GetProtocol) +} + +var grpcProtocol *GrpcProtocol + +// GrpcProtocol ... +type GrpcProtocol struct { + protocol.BaseProtocol + serverMap map[string]*Server + serverLock sync.Mutex +} + +// NewGRPCProtocol ... +func NewGRPCProtocol() *GrpcProtocol { + return &GrpcProtocol{ + BaseProtocol: protocol.NewBaseProtocol(), + serverMap: make(map[string]*Server), + } +} + +// Export ... +func (gp *GrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + url := invoker.GetUrl() + serviceKey := url.ServiceKey() + exporter := NewGrpcExporter(serviceKey, invoker, gp.ExporterMap()) + gp.SetExporterMap(serviceKey, exporter) + logger.Infof("Export service: %s", url.String()) + gp.openServer(url) + return exporter +} + +func (gp *GrpcProtocol) openServer(url common.URL) { + _, ok := gp.serverMap[url.Location] + if !ok { + _, ok := gp.ExporterMap().Load(url.ServiceKey()) + if !ok { + panic("[GrpcProtocol]" + url.Key() + "is not existing") + } + + gp.serverLock.Lock() + _, ok = gp.serverMap[url.Location] + if !ok { + srv := NewServer() + gp.serverMap[url.Location] = srv + srv.Start(url) + } + gp.serverLock.Unlock() + } +} + +// Refer ... +func (gp *GrpcProtocol) Refer(url common.URL) protocol.Invoker { + invoker := NewGrpcInvoker(url, NewClient(url)) + gp.SetInvokers(invoker) + logger.Infof("Refer service: %s", url.String()) + return invoker +} + +// Destroy ... +func (gp *GrpcProtocol) Destroy() { + logger.Infof("GrpcProtocol destroy.") + + gp.BaseProtocol.Destroy() + + for key, server := range gp.serverMap { + delete(gp.serverMap, key) + server.Stop() + } +} + +// GetProtocol ... +func GetProtocol() protocol.Protocol { + if grpcProtocol == nil { + grpcProtocol = NewGRPCProtocol() + } + return grpcProtocol +} diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d0206a0fd953e40a478c26a2298f4889d8f72771 --- /dev/null +++ b/protocol/grpc/grpc_protocol_test.go @@ -0,0 +1,84 @@ +/* +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 grpc + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/grpc/internal" +) + +func TestGrpcProtocol_Export(t *testing.T) { + // Export + addService() + + proto := GetProtocol() + 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) + + // make sure url + eq := exporter.GetInvoker().GetUrl().URLEqual(url) + assert.True(t, eq) + + // make sure exporterMap after 'Unexport' + _, ok := proto.(*GrpcProtocol).ExporterMap().Load(url.ServiceKey()) + assert.True(t, ok) + exporter.Unexport() + _, ok = proto.(*GrpcProtocol).ExporterMap().Load(url.ServiceKey()) + assert.False(t, ok) + + // make sure serverMap after 'Destroy' + _, ok = proto.(*GrpcProtocol).serverMap[url.Location] + assert.True(t, ok) + proto.Destroy() + _, ok = proto.(*GrpcProtocol).serverMap[url.Location] + assert.False(t, ok) +} + +func TestGrpcProtocol_Refer(t *testing.T) { + go internal.InitGrpcServer() + defer internal.ShutdownGrpcServer() + time.Sleep(time.Second) + + proto := GetProtocol() + 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) + + // make sure url + eq := invoker.GetUrl().URLEqual(url) + assert.True(t, eq) + + // make sure invokers after 'Destroy' + invokersLen := len(proto.(*GrpcProtocol).Invokers()) + assert.Equal(t, 1, invokersLen) + proto.Destroy() + invokersLen = len(proto.(*GrpcProtocol).Invokers()) + assert.Equal(t, 0, invokersLen) +} diff --git a/protocol/grpc/internal/client.go b/protocol/grpc/internal/client.go new file mode 100644 index 0000000000000000000000000000000000000000..d236e3046a90e9179fba07a0be5edb07f8c2a3e8 --- /dev/null +++ b/protocol/grpc/internal/client.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 internal + +import ( + "context" +) + +import ( + "google.golang.org/grpc" +) + +import ( + "github.com/apache/dubbo-go/config" +) + +func init() { + config.SetConsumerService(&GrpcGreeterImpl{}) +} + +// GrpcGreeterImpl +//used for dubbo-grpc biz client +type GrpcGreeterImpl struct { + SayHello func(ctx context.Context, in *HelloRequest, out *HelloReply) error +} + +// Reference ... +func (u *GrpcGreeterImpl) Reference() string { + return "GrpcGreeterImpl" +} + +// GetDubboStub ... +func (u *GrpcGreeterImpl) GetDubboStub(cc *grpc.ClientConn) GreeterClient { + return NewGreeterClient(cc) +} diff --git a/protocol/grpc/internal/doc.go b/protocol/grpc/internal/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..f2ef2ebd5e41980e1e1f1b0071ca7bb3885539f7 --- /dev/null +++ b/protocol/grpc/internal/doc.go @@ -0,0 +1,19 @@ +/* +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. +*/ + +// just for test, never use internal for production. +package internal diff --git a/protocol/grpc/internal/helloworld.pb.go b/protocol/grpc/internal/helloworld.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..79b74ac65011208ae74f989cf86e4e6f9f446015 --- /dev/null +++ b/protocol/grpc/internal/helloworld.pb.go @@ -0,0 +1,227 @@ +/* +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. +*/ + +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: helloworld.proto + +package internal + +import ( + "context" + "fmt" + "math" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{0} +} + +func (m *HelloRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloRequest.Unmarshal(m, b) +} +func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) +} +func (m *HelloRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloRequest.Merge(m, src) +} +func (m *HelloRequest) XXX_Size() int { + return xxx_messageInfo_HelloRequest.Size(m) +} +func (m *HelloRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HelloRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloRequest proto.InternalMessageInfo + +func (m *HelloRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{1} +} + +func (m *HelloReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloReply.Unmarshal(m, b) +} +func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) +} +func (m *HelloReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloReply.Merge(m, src) +} +func (m *HelloReply) XXX_Size() int { + return xxx_messageInfo_HelloReply.Size(m) +} +func (m *HelloReply) XXX_DiscardUnknown() { + xxx_messageInfo_HelloReply.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloReply proto.InternalMessageInfo + +func (m *HelloReply) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func init() { + proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) } + +var fileDescriptor_17b8c58d586b62f2 = []byte{ + // 175 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, + 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, + 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, + 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, + 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, + 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, + 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, + 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6, + 0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9, + 0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb, + 0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +// UnimplementedGreeterServer can be embedded to have forward compatible implementations. +type UnimplementedGreeterServer struct { +} + +func (*UnimplementedGreeterServer) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} diff --git a/protocol/grpc/internal/server.go b/protocol/grpc/internal/server.go new file mode 100644 index 0000000000000000000000000000000000000000..a0759f757dc44153e7f09b726db5e66176796c96 --- /dev/null +++ b/protocol/grpc/internal/server.go @@ -0,0 +1,66 @@ +/* +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 internal + +import ( + "context" + "log" + "net" +) + +import ( + "google.golang.org/grpc" +) + +var ( + s *grpc.Server +) + +// server is used to implement helloworld.GreeterServer. +type server struct { + UnimplementedGreeterServer +} + +// SayHello implements helloworld.GreeterServer +func (s *server) SayHello(ctx context.Context, in *HelloRequest) (*HelloReply, error) { + log.Printf("Received: %v", in.GetName()) + return &HelloReply{Message: "Hello " + in.GetName()}, nil +} + +// InitGrpcServer ... +func InitGrpcServer() { + port := ":30000" + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s = grpc.NewServer() + RegisterGreeterServer(s, &server{}) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +// ShutdownGrpcServer ... +func ShutdownGrpcServer() { + if s == nil { + return + } + s.GracefulStop() +} diff --git a/protocol/grpc/protoc-gen-dubbo/examples/Makefile b/protocol/grpc/protoc-gen-dubbo/examples/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7893bbc51aa436f711bfb653dc81d9ec66b7e5c0 --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/examples/Makefile @@ -0,0 +1,19 @@ +# 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. + +grpc-gen: + protoc -I ./ helloworld.proto --go_out=plugins=grpc:. +dubbo-gen: + protoc -I ./ helloworld.proto --dubbo_out=plugins=grpc+dubbo:. diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..f5d3a49b0916050fc6b2e6373fde0b70df0a1c31 --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go @@ -0,0 +1,301 @@ +/* +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. +*/ + +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: helloworld.proto + +package main + +import ( + "context" + "fmt" + "math" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +import ( + "github.com/apache/dubbo-go/protocol" + dgrpc "github.com/apache/dubbo-go/protocol/grpc" + "github.com/apache/dubbo-go/protocol/invocation" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{0} +} + +func (m *HelloRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloRequest.Unmarshal(m, b) +} +func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) +} +func (m *HelloRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloRequest.Merge(m, src) +} +func (m *HelloRequest) XXX_Size() int { + return xxx_messageInfo_HelloRequest.Size(m) +} +func (m *HelloRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HelloRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloRequest proto.InternalMessageInfo + +func (m *HelloRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { + return fileDescriptor_17b8c58d586b62f2, []int{1} +} + +func (m *HelloReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HelloReply.Unmarshal(m, b) +} +func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) +} +func (m *HelloReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_HelloReply.Merge(m, src) +} +func (m *HelloReply) XXX_Size() int { + return xxx_messageInfo_HelloReply.Size(m) +} +func (m *HelloReply) XXX_DiscardUnknown() { + xxx_messageInfo_HelloReply.DiscardUnknown(m) +} + +var xxx_messageInfo_HelloReply proto.InternalMessageInfo + +func (m *HelloReply) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func init() { + proto.RegisterType((*HelloRequest)(nil), "main.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "main.HelloReply") +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) } + +var fileDescriptor_17b8c58d586b62f2 = []byte{ + // 185 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, + 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, + 0xcc, 0xcc, 0x53, 0x52, 0xe2, 0xe2, 0xf1, 0x00, 0xc9, 0x04, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, + 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, + 0xd9, 0x4a, 0x6a, 0x5c, 0x5c, 0x50, 0x35, 0x05, 0x39, 0x95, 0x42, 0x12, 0x5c, 0xec, 0xb9, 0xa9, + 0xc5, 0xc5, 0x89, 0xe9, 0x30, 0x45, 0x30, 0xae, 0x91, 0x2d, 0x17, 0xbb, 0x7b, 0x51, 0x6a, 0x6a, + 0x49, 0x6a, 0x91, 0x90, 0x11, 0x17, 0x47, 0x70, 0x62, 0x25, 0x58, 0x97, 0x90, 0x90, 0x1e, 0xc8, + 0x26, 0x3d, 0x64, 0x6b, 0xa4, 0x04, 0x50, 0xc4, 0x0a, 0x72, 0x2a, 0x95, 0x18, 0x9c, 0xcc, 0xb8, + 0xa4, 0x33, 0xf3, 0xf5, 0xd2, 0x8b, 0x0a, 0x92, 0xf5, 0x52, 0x2b, 0x12, 0x73, 0x0b, 0x72, 0x52, + 0x8b, 0xf5, 0x10, 0xae, 0x76, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x1e, 0x08, 0x60, + 0x5c, 0xc4, 0xc4, 0xec, 0xe1, 0x13, 0x9e, 0xc4, 0x06, 0xf6, 0x8f, 0x31, 0x20, 0x00, 0x00, 0xff, + 0xff, 0xd2, 0x16, 0x5f, 0x34, 0xe3, 0x00, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := c.cc.Invoke(ctx, "/main.Greeter/SayHello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +// UnimplementedGreeterServer can be embedded to have forward compatible implementations. +type UnimplementedGreeterServer struct { +} + +func (*UnimplementedGreeterServer) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/main.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "main.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} + +// GreeterClientImpl is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GreeterClientImpl struct { + // Sends a greeting + SayHello func(ctx context.Context, in *HelloRequest, out *HelloReply) error +} + +func (c *GreeterClientImpl) Reference() string { + return "greeterImpl" +} + +func (c *GreeterClientImpl) GetDubboStub(cc *grpc.ClientConn) GreeterClient { + return NewGreeterClient(cc) +} + +type GreeterProviderBase struct { + proxyImpl protocol.Invoker +} + +func (s *GreeterProviderBase) SetProxyImpl(impl protocol.Invoker) { + s.proxyImpl = impl +} + +func (s *GreeterProviderBase) GetProxyImpl() protocol.Invoker { + return s.proxyImpl +} + +func _DUBBO_Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + base := srv.(dgrpc.DubboGrpcService) + args := []interface{}{} + args = append(args, in) + invo := invocation.NewRPCInvocation("SayHello", args, nil) + if interceptor == nil { + result := base.GetProxyImpl().Invoke(context.Background(), invo) + return result.Result(), result.Error() + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/main.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + result := base.GetProxyImpl().Invoke(context.Background(), invo) + return result.Result(), result.Error() + } + return interceptor(ctx, in, info, handler) +} + +func (s *GreeterProviderBase) ServiceDesc() *grpc.ServiceDesc { + return &grpc.ServiceDesc{ + ServiceName: "main.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _DUBBO_Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", + } +} diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto new file mode 100644 index 0000000000000000000000000000000000000000..e73f72b1e06c90bd917e905f992efbddd744b4ad --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto @@ -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. +*/ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package main; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/protocol/grpc/protoc-gen-dubbo/main.go b/protocol/grpc/protoc-gen-dubbo/main.go new file mode 100644 index 0000000000000000000000000000000000000000..b2f0e82f74a4d3c1a7013714cd18d83562baff71 --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/main.go @@ -0,0 +1,74 @@ +/* +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 main + +import ( + "io/ioutil" + "os" +) + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/generator" + _ "github.com/golang/protobuf/protoc-gen-go/grpc" +) + +import ( + _ "github.com/apache/dubbo-go/protocol/grpc/protoc-gen-dubbo/plugin/dubbo" +) + +func main() { + // Begin by allocating a generate. The request and response structures are stored there + // so we can do error handling easily - the response structure contains the field to + // report failure. + g := generator.New() + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + g.Error(err, "reading input") + } + + if err := proto.Unmarshal(data, g.Request); err != nil { + g.Error(err, "parsing input proto") + } + + if len(g.Request.FileToGenerate) == 0 { + g.Fail("no files to generate") + } + + g.CommandLineParameters(g.Request.GetParameter()) + + // Create a wrapped version of the Descriptors and EnumDescriptors that + // point to the file that defines them. + g.WrapTypes() + + g.SetPackageNames() + g.BuildTypeNameMap() + + g.GenerateAllFiles() + + // Send back the results. + data, err = proto.Marshal(g.Response) + if err != nil { + g.Error(err, "failed to marshal output proto") + } + _, err = os.Stdout.Write(data) + if err != nil { + g.Error(err, "failed to write output proto") + } +} diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..064c738a53d2200223b0ca81aca77358afad032b --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go @@ -0,0 +1,19 @@ +/* +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 dubbo plugin for protobuf. +package dubbo diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go new file mode 100644 index 0000000000000000000000000000000000000000..e84a7d0cc96887cf728f499c28c26f061ed1ccdf --- /dev/null +++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go @@ -0,0 +1,346 @@ +/* +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 dubbo + +import ( + "fmt" + "strconv" + "strings" +) + +import ( + pb "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/golang/protobuf/protoc-gen-go/generator" +) + +// generatedCodeVersion indicates a version of the generated code. +// It is incremented whenever an incompatibility between the generated code and +// the grpc package is introduced; the generated code references +// a constant, grpc.SupportPackageIsVersionN (where N is generatedCodeVersion). +const generatedCodeVersion = 4 + +// Paths for packages used by code generated in this file, +// relative to the import_prefix of the generator.Generator. +const ( + contextPkgPath = "context" + grpcPkgPath = "google.golang.org/grpc" + codePkgPath = "google.golang.org/grpc/codes" + statusPkgPath = "google.golang.org/grpc/status" +) + +func init() { + generator.RegisterPlugin(new(dubboGrpc)) +} + +// grpc is an implementation of the Go protocol buffer compiler's +// plugin architecture. It generates bindings for gRPC-dubbo support. +type dubboGrpc struct { + gen *generator.Generator +} + +// Name returns the name of this plugin, "grpc". +func (g *dubboGrpc) Name() string { + return "dubbo" +} + +// The names for packages imported in the generated code. +// They may vary from the final path component of the import path +// if the name is used by other packages. +var ( + contextPkg string + grpcPkg string +) + +// Init initializes the plugin. +func (g *dubboGrpc) Init(gen *generator.Generator) { + g.gen = gen +} + +// Given a type name defined in a .proto, return its object. +// Also record that we're using it, to guarantee the associated import. +func (g *dubboGrpc) objectNamed(name string) generator.Object { + g.gen.RecordTypeUse(name) + return g.gen.ObjectNamed(name) +} + +// Given a type name defined in a .proto, return its name as we will print it. +func (g *dubboGrpc) typeName(str string) string { + return g.gen.TypeName(g.objectNamed(str)) +} + +// P forwards to g.gen.P. +func (g *dubboGrpc) P(args ...interface{}) { g.gen.P(args...) } + +// Generate generates code for the services in the given file. +// be consistent with grpc plugin +func (g *dubboGrpc) Generate(file *generator.FileDescriptor) { + if len(file.FileDescriptorProto.Service) == 0 { + return + } + + contextPkg = string(g.gen.AddImport(contextPkgPath)) + grpcPkg = string(g.gen.AddImport(grpcPkgPath)) + + for i, service := range file.FileDescriptorProto.Service { + g.generateService(file, service, i) + } +} + +// GenerateImports generates the import declaration for this file. +func (g *dubboGrpc) GenerateImports(file *generator.FileDescriptor) { + g.P("import (") + g.P(`dgrpc "github.com/apache/dubbo-go/protocol/grpc"`) + g.P(`"github.com/apache/dubbo-go/protocol/invocation"`) + g.P(`"github.com/apache/dubbo-go/protocol"`) + g.P(`"github.com/apache/dubbo-go/config"`) + g.P(` ) `) +} + +func unexport(s string) string { return strings.ToLower(s[:1]) + s[1:] } + +// deprecationComment is the standard comment added to deprecated +// messages, fields, enums, and enum values. +var deprecationComment = "// Deprecated: Do not use." + +// generateService generates all the code for the named service. +func (g *dubboGrpc) generateService(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int) { + path := fmt.Sprintf("6,%d", index) // 6 means service. + + origServName := service.GetName() + fullServName := origServName + if pkg := file.GetPackage(); pkg != "" { + fullServName = pkg + "." + fullServName + } + servName := generator.CamelCase(origServName) + deprecated := service.GetOptions().GetDeprecated() + + g.P() + g.P(fmt.Sprintf(`// %sClientImpl is the client API for %s service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.`, servName, servName)) + + // Client interface. + if deprecated { + g.P("//") + g.P(deprecationComment) + } + dubboSrvName := servName + "ClientImpl" + g.P("type ", dubboSrvName, " struct {") + for i, method := range service.Method { + g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service. + if method.GetOptions().GetDeprecated() { + g.P("//") + g.P(deprecationComment) + } + g.P(g.generateClientSignature(servName, method)) + } + g.P("}") + g.P() + + // NewClient factory. + if deprecated { + g.P(deprecationComment) + } + + // add Reference method + //func (u *GrpcGreeterImpl) Reference() string { + // return "GrpcGreeterImpl" + //} + g.P("func (c *", dubboSrvName, ") ", " Reference() string ", "{") + g.P(`return "`, unexport(servName), `Impl"`) + g.P("}") + g.P() + + // add GetDubboStub method + // func (u *GrpcGreeterImpl) GetDubboStub(cc *grpc.ClientConn) GreeterClient { + // return NewGreeterClient(cc) + //} + g.P("func (c *", dubboSrvName, ") ", " GetDubboStub(cc *grpc.ClientConn) ", servName, "Client {") + g.P(`return New`, servName, `Client(cc)`) + g.P("}") + g.P() + + // Server interface. + serverType := servName + "ProviderBase" + g.P("type ", serverType, " struct {") + g.P("proxyImpl protocol.Invoker") + g.P("}") + g.P() + + // add set method + //func (g *GreeterProviderBase) SetProxyImpl(impl protocol.Invoker) { + // g.proxyImpl = impl + //} + g.P("func (s *", serverType, ") SetProxyImpl(impl protocol.Invoker) {") + g.P(`s.proxyImpl = impl`) + g.P("}") + g.P() + + // return get method + g.P("func (s *", serverType, ") GetProxyImpl() protocol.Invoker {") + g.P(`return s.proxyImpl`) + g.P("}") + g.P() + + // add handler + var handlerNames []string + for _, method := range service.Method { + hname := g.generateServerMethod(servName, fullServName, method) + handlerNames = append(handlerNames, hname) + } + + grpcserverType := servName + "Server" + // return service desc + g.P("func (s *", serverType, ") ServiceDesc() *grpc.ServiceDesc {") + g.P(`return &grpc.ServiceDesc{`) + g.P("ServiceName: ", strconv.Quote(fullServName), ",") + g.P("HandlerType: (*", grpcserverType, ")(nil),") + g.P("Methods: []", grpcPkg, ".MethodDesc{") + for i, method := range service.Method { + if method.GetServerStreaming() || method.GetClientStreaming() { + continue + } + g.P("{") + g.P("MethodName: ", strconv.Quote(method.GetName()), ",") + g.P("Handler: ", handlerNames[i], ",") + g.P("},") + } + g.P("},") + g.P("Streams: []", grpcPkg, ".StreamDesc{},") + g.P("Metadata: \"", file.GetName(), "\",") + g.P("}") + g.P("}") + g.P() +} + +// generateClientSignature returns the client-side signature for a method. +func (g *dubboGrpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string { + origMethName := method.GetName() + methName := generator.CamelCase(origMethName) + //if reservedClientName[methName] { + // methName += "_" + //} + reqArg := ", in *" + g.typeName(method.GetInputType()) + if method.GetClientStreaming() { + reqArg = "" + } + respName := "out *" + g.typeName(method.GetOutputType()) + if method.GetServerStreaming() || method.GetClientStreaming() { + respName = servName + "_" + generator.CamelCase(origMethName) + "Client" + } + return fmt.Sprintf("%s func(ctx %s.Context%s, %s) error", methName, contextPkg, reqArg, respName) +} + +func (g *dubboGrpc) generateClientMethod(servName, fullServName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) { +} + +func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method *pb.MethodDescriptorProto) string { + methName := generator.CamelCase(method.GetName()) + hname := fmt.Sprintf("_DUBBO_%s_%s_Handler", servName, methName) + inType := g.typeName(method.GetInputType()) + outType := g.typeName(method.GetOutputType()) + + if !method.GetServerStreaming() && !method.GetClientStreaming() { + g.P("func ", hname, "(srv interface{}, ctx ", contextPkg, ".Context, dec func(interface{}) error, interceptor ", grpcPkg, ".UnaryServerInterceptor) (interface{}, error) {") + g.P("in := new(", inType, ")") + g.P("if err := dec(in); err != nil { return nil, err }") + + g.P("base := srv.(dgrpc.DubboGrpcService)") + g.P("args := []interface{}{}") + g.P("args = append(args, in)") + g.P(`invo := invocation.NewRPCInvocation("`, methName, `", args, nil)`) + + g.P("if interceptor == nil {") + g.P("result := base.GetProxyImpl().Invoke(invo)") + g.P("return result.Result(), result.Error()") + g.P("}") + + g.P("info := &", grpcPkg, ".UnaryServerInfo{") + g.P("Server: srv,") + g.P("FullMethod: ", strconv.Quote(fmt.Sprintf("/%s/%s", fullServName, methName)), ",") + g.P("}") + + g.P("handler := func(ctx ", contextPkg, ".Context, req interface{}) (interface{}, error) {") + g.P("result := base.GetProxyImpl().Invoke(invo)") + g.P("return result.Result(), result.Error()") + g.P("}") + + g.P("return interceptor(ctx, in, info, handler)") + g.P("}") + g.P() + return hname + } + streamType := unexport(servName) + methName + "Server" + g.P("func ", hname, "(srv interface{}, stream ", grpcPkg, ".ServerStream) error {") + if !method.GetClientStreaming() { + g.P("m := new(", inType, ")") + g.P("if err := stream.RecvMsg(m); err != nil { return err }") + g.P("return srv.(", servName, "Server).", methName, "(m, &", streamType, "{stream})") + } else { + g.P("return srv.(", servName, "Server).", methName, "(&", streamType, "{stream})") + } + g.P("}") + g.P() + + genSend := method.GetServerStreaming() + genSendAndClose := !method.GetServerStreaming() + genRecv := method.GetClientStreaming() + + // Stream auxiliary types and methods. + g.P("type ", servName, "_", methName, "Server interface {") + if genSend { + g.P("Send(*", outType, ") error") + } + if genSendAndClose { + g.P("SendAndClose(*", outType, ") error") + } + if genRecv { + g.P("Recv() (*", inType, ", error)") + } + g.P(grpcPkg, ".ServerStream") + g.P("}") + g.P() + + g.P("type ", streamType, " struct {") + g.P(grpcPkg, ".ServerStream") + g.P("}") + g.P() + + if genSend { + g.P("func (x *", streamType, ") Send(m *", outType, ") error {") + g.P("return x.ServerStream.SendMsg(m)") + g.P("}") + g.P() + } + if genSendAndClose { + g.P("func (x *", streamType, ") SendAndClose(m *", outType, ") error {") + g.P("return x.ServerStream.SendMsg(m)") + g.P("}") + g.P() + } + if genRecv { + g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {") + g.P("m := new(", inType, ")") + g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }") + g.P("return m, nil") + g.P("}") + g.P() + } + + return hname +} diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go new file mode 100644 index 0000000000000000000000000000000000000000..19b9db4ac743ceefcf035d399c0bbcdd99f1fa80 --- /dev/null +++ b/protocol/grpc/server.go @@ -0,0 +1,106 @@ +/* +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 grpc + +import ( + "fmt" + "net" + "reflect" +) + +import ( + "google.golang.org/grpc" +) + +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" + "github.com/apache/dubbo-go/protocol" +) + +// Server ... +type Server struct { + grpcServer *grpc.Server +} + +// NewServer ... +func NewServer() *Server { + return &Server{} +} + +// DubboGrpcService ... +type DubboGrpcService interface { + SetProxyImpl(impl protocol.Invoker) + GetProxyImpl() protocol.Invoker + ServiceDesc() *grpc.ServiceDesc +} + +// Start ... +func (s *Server) Start(url common.URL) { + var ( + addr string + err error + ) + addr = url.Location + lis, err := net.Listen("tcp", addr) + if err != nil { + panic(err) + } + server := grpc.NewServer() + + key := url.GetParam(constant.BEAN_NAME_KEY, "") + service := config.GetProviderService(key) + + ds, ok := service.(DubboGrpcService) + if !ok { + panic("illegal service type registered") + } + + m, ok := reflect.TypeOf(service).MethodByName("SetProxyImpl") + if !ok { + panic("method SetProxyImpl is necessary for grpc service") + } + + exporter, _ := grpcProtocol.ExporterMap().Load(url.ServiceKey()) + if exporter == nil { + panic(fmt.Sprintf("no exporter found for servicekey: %v", url.ServiceKey())) + } + invoker := exporter.(protocol.Exporter).GetInvoker() + if invoker == nil { + panic(fmt.Sprintf("no invoker found for servicekey: %v", url.ServiceKey())) + } + in := []reflect.Value{reflect.ValueOf(service)} + in = append(in, reflect.ValueOf(invoker)) + m.Func.Call(in) + + server.RegisterService(ds.ServiceDesc(), service) + + s.grpcServer = server + go func() { + if err = server.Serve(lis); err != nil { + logger.Errorf("server serve failed with err: %v", err) + } + }() +} + +// Stop ... +func (s *Server) Stop() { + s.grpcServer.Stop() +} diff --git a/protocol/invocation.go b/protocol/invocation.go index 055e7a4cd18707772d6ba75303053f15dc55dbe3..f32f2c3449ac063ecb89952bd4653312a07a3df4 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -21,9 +21,11 @@ import ( "reflect" ) +// Invocation ... type Invocation interface { MethodName() string ParameterTypes() []reflect.Type + ParameterValues() []reflect.Value Arguments() []interface{} Reply() interface{} Attachments() map[string]string diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index 2124a22f1611b24d7f4370de64b117c58c4f7e7b..b207fd0b0cc4eb7de8409a8c46c6fc9ef0baa5c7 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -19,26 +19,32 @@ package invocation import ( "reflect" + "sync" ) import ( "github.com/apache/dubbo-go/protocol" ) -///////////////////////////// +// /////////////////////////// // Invocation Impletment of RPC -///////////////////////////// +// /////////////////////////// + // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation +// RPCInvocation ... type RPCInvocation struct { - methodName string - parameterTypes []reflect.Type - arguments []interface{} - reply interface{} - callBack interface{} - attachments map[string]string - invoker protocol.Invoker -} - + methodName string + parameterTypes []reflect.Type + parameterValues []reflect.Value + arguments []interface{} + reply interface{} + callBack interface{} + attachments map[string]string + invoker protocol.Invoker + lock sync.RWMutex +} + +// NewRPCInvocation ... func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]string) *RPCInvocation { return &RPCInvocation{ methodName: methodName, @@ -47,6 +53,7 @@ func NewRPCInvocation(methodName string, arguments []interface{}, attachments ma } } +// NewRPCInvocationWithOptions ... func NewRPCInvocationWithOptions(opts ...option) *RPCInvocation { invo := &RPCInvocation{} for _, opt := range opts { @@ -55,31 +62,45 @@ func NewRPCInvocationWithOptions(opts ...option) *RPCInvocation { return invo } +// MethodName ... func (r *RPCInvocation) MethodName() string { return r.methodName } +// ParameterTypes ... func (r *RPCInvocation) ParameterTypes() []reflect.Type { return r.parameterTypes } +// ParameterValues ... +func (r *RPCInvocation) ParameterValues() []reflect.Value { + return r.parameterValues +} + +// Arguments ... func (r *RPCInvocation) Arguments() []interface{} { return r.arguments } +// Reply ... func (r *RPCInvocation) Reply() interface{} { return r.reply } +// SetReply ... func (r *RPCInvocation) SetReply(reply interface{}) { r.reply = reply } +// Attachments ... func (r *RPCInvocation) Attachments() map[string]string { return r.attachments } +// AttachmentsByKey ... func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string { + r.lock.RLock() + defer r.lock.RUnlock() if r.attachments == nil { return defaultValue } @@ -90,71 +111,92 @@ func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string return defaultValue } +// SetAttachments ... func (r *RPCInvocation) SetAttachments(key string, value string) { + r.lock.Lock() + defer r.lock.Unlock() if r.attachments == nil { r.attachments = make(map[string]string) } r.attachments[key] = value } +// Invoker ... func (r *RPCInvocation) Invoker() protocol.Invoker { return r.invoker } +// SetInvoker ... func (r *RPCInvocation) SetInvoker() protocol.Invoker { return r.invoker } +// CallBack ... func (r *RPCInvocation) CallBack() interface{} { return r.callBack } +// SetCallBack ... func (r *RPCInvocation) SetCallBack(c interface{}) { r.callBack = c } -/////////////////////////// +// ///////////////////////// // option -/////////////////////////// +// ///////////////////////// type option func(invo *RPCInvocation) +// WithMethodName ... func WithMethodName(methodName string) option { return func(invo *RPCInvocation) { invo.methodName = methodName } } +// WithParameterTypes ... func WithParameterTypes(parameterTypes []reflect.Type) option { return func(invo *RPCInvocation) { invo.parameterTypes = parameterTypes } } +// WithParameterValues ... +func WithParameterValues(parameterValues []reflect.Value) option { + return func(invo *RPCInvocation) { + invo.parameterValues = parameterValues + } +} + +// WithArguments ... func WithArguments(arguments []interface{}) option { return func(invo *RPCInvocation) { invo.arguments = arguments } } +// WithReply ... func WithReply(reply interface{}) option { return func(invo *RPCInvocation) { invo.reply = reply } } +// WithCallBack ... func WithCallBack(callBack interface{}) option { return func(invo *RPCInvocation) { invo.callBack = callBack } } +// WithAttachments ... func WithAttachments(attachments map[string]string) option { return func(invo *RPCInvocation) { invo.attachments = attachments } } +// WithInvoker ... func WithInvoker(invoker protocol.Invoker) option { return func(invo *RPCInvocation) { invo.invoker = invoker diff --git a/protocol/invoker.go b/protocol/invoker.go index f5d41a09ad2778c12c7e5e68167a4d0acc9e3f4c..bb71bab1cfa2ede7fb035912ae996f9adb7411e0 100644 --- a/protocol/invoker.go +++ b/protocol/invoker.go @@ -17,28 +17,35 @@ package protocol +import ( + "context" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/logger" ) +// Invoker ... //go:generate mockgen -source invoker.go -destination mock/mock_invoker.go -self_package github.com/apache/dubbo-go/protocol/mock --package mock Invoker // Extension - Invoker type Invoker interface { common.Node - Invoke(Invocation) Result + Invoke(context.Context, Invocation) Result } ///////////////////////////// // base invoker ///////////////////////////// +// BaseInvoker ... type BaseInvoker struct { url common.URL available bool destroyed bool } +// NewBaseInvoker ... func NewBaseInvoker(url common.URL) *BaseInvoker { return &BaseInvoker{ url: url, @@ -47,22 +54,27 @@ func NewBaseInvoker(url common.URL) *BaseInvoker { } } +// GetUrl ... func (bi *BaseInvoker) GetUrl() common.URL { return bi.url } +// IsAvailable ... func (bi *BaseInvoker) IsAvailable() bool { return bi.available } +// IsDestroyed ... func (bi *BaseInvoker) IsDestroyed() bool { return bi.destroyed } -func (bi *BaseInvoker) Invoke(invocation Invocation) Result { +// Invoke ... +func (bi *BaseInvoker) Invoke(context context.Context, invocation Invocation) Result { return &RPCResult{} } +// Destroy ... func (bi *BaseInvoker) Destroy() { logger.Infof("Destroy invoker: %s", bi.GetUrl().String()) bi.destroyed = true diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index b64a3a344e95164b8f49556cdc155bf34642a83b..ba7197dbc857c2ed7acda1a9f246a5b826e86915 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -33,18 +33,21 @@ import ( ) import ( + "github.com/opentracing/opentracing-go" 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" ) -////////////////////////////////////////////// +// //////////////////////////////////////////// // Request -////////////////////////////////////////////// +// //////////////////////////////////////////// +// Request ... type Request struct { ID int64 group string @@ -56,25 +59,28 @@ type Request struct { contentType string } -////////////////////////////////////////////// +// //////////////////////////////////////////// // HTTP Client -////////////////////////////////////////////// +// //////////////////////////////////////////// +// HTTPOptions ... type HTTPOptions struct { HandshakeTimeout time.Duration HTTPTimeout time.Duration } var defaultHTTPOptions = HTTPOptions{ - HandshakeTimeout: 3e9, - HTTPTimeout: 3e9, + HandshakeTimeout: 3 * time.Second, + HTTPTimeout: 3 * time.Second, } +// HTTPClient ... type HTTPClient struct { ID int64 options HTTPOptions } +// NewHTTPClient ... func NewHTTPClient(opt *HTTPOptions) *HTTPClient { if opt == nil { opt = &defaultHTTPOptions @@ -94,6 +100,7 @@ func NewHTTPClient(opt *HTTPOptions) *HTTPClient { } } +// NewRequest ... func (c *HTTPClient) NewRequest(service common.URL, method string, args interface{}) *Request { return &Request{ @@ -107,6 +114,7 @@ func (c *HTTPClient) NewRequest(service common.URL, method string, args interfac } } +// Call ... func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, rsp interface{}) error { // header httpHeader := http.Header{} @@ -115,7 +123,7 @@ func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, reqTimeout := c.options.HTTPTimeout if reqTimeout <= 0 { - reqTimeout = 1e8 + reqTimeout = 100 * time.Millisecond } httpHeader.Set("Timeout", reqTimeout.String()) if md, ok := ctx.Value(constant.DUBBOGO_CTX_KEY).(map[string]string); ok { @@ -124,6 +132,13 @@ func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, } } + if span := opentracing.SpanFromContext(ctx); span != nil { + err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(httpHeader)) + if err != nil { + logger.Error("Could not inject the Context into http header.") + } + } + // body codec := newJsonClientCodec() codecData := CodecData{ @@ -144,6 +159,7 @@ func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, return perrors.WithStack(codec.Read(rspBody, rsp)) } +// Do // !!The high level of complexity and the likelihood that the fasthttp client has not been extensively used // in production means that you would need to expect a very large benefit to justify the adoption of fasthttp today. func (c *HTTPClient) Do(addr, path string, httpHeader http.Header, body []byte) ([]byte, error) { diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 9be55e247a730460a3adee5622fa978ef2defbfb..0cb88b36a8f330059906eb70417b6d4841020c38 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -25,6 +25,7 @@ import ( ) import ( + "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -55,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{ @@ -74,6 +75,7 @@ func TestHTTPClient_Call(t *testing.T) { "X-Services": url.Path, "X-Method": "GetUser", }) + req := client.NewRequest(url, "GetUser", []interface{}{"1", "username"}) reply := &User{} err = client.Call(ctx, url, req, reply) @@ -147,6 +149,10 @@ func TestHTTPClient_Call(t *testing.T) { "X-Services": url.Path, "X-Method": "GetUser4", }) + + span := opentracing.StartSpan("Test-Inject-Tracing-ID") + ctx = opentracing.ContextWithSpan(ctx, span) + req = client.NewRequest(url, "GetUser4", []interface{}{1}) reply = &User{} err = client.Call(ctx, url, req, reply) diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go index 7ee454e8ad16d2ee96ed08e7e5f55b2209a81054..d1c2a858b4e4223ac32fc1160b56f6ee1862c8ce 100644 --- a/protocol/jsonrpc/json.go +++ b/protocol/jsonrpc/json.go @@ -31,10 +31,13 @@ import ( ) const ( + // MAX_JSONRPC_ID max jsonrpc request/response id MAX_JSONRPC_ID = 0x7FFFFFFF - VERSION = "2.0" + // VERSION jsonrpc version + VERSION = "2.0" ) +// CodecData ... type CodecData struct { ID int64 Method string @@ -54,7 +57,7 @@ const ( codeServerErrorEnd = -32000 ) -// rsponse Error +// Error response Error type Error struct { Code int `json:"code"` Message string `json:"message"` @@ -278,12 +281,14 @@ type serverResponse struct { Error interface{} `json:"error,omitempty"` } +// ServerCodec ... type ServerCodec struct { req serverRequest } var ( - null = json.RawMessage([]byte("null")) + null = json.RawMessage([]byte("null")) + // Version ... Version = "2.0" ) @@ -291,6 +296,7 @@ func newServerCodec() *ServerCodec { return &ServerCodec{} } +// ReadHeader ... func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error { if header["HttpMethod"] != "POST" { return &Error{Code: -32601, Message: "Method not found"} @@ -322,6 +328,7 @@ func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error { return nil } +// ReadBody ... func (c *ServerCodec) ReadBody(x interface{}) error { // If x!=nil and return error e: // - Write() will be called with e.Error() in r.Error @@ -355,6 +362,7 @@ func (c *ServerCodec) ReadBody(x interface{}) error { return nil } +// NewError ... func NewError(code int, message string) *Error { return &Error{Code: code, Message: message} } diff --git a/protocol/jsonrpc/jsonrpc_exporter.go b/protocol/jsonrpc/jsonrpc_exporter.go index 6720330494a3b833d4a67d8b2408377ce62b1ddf..7f8fd491854f1ab25e63410a22ef5664db92f614 100644 --- a/protocol/jsonrpc/jsonrpc_exporter.go +++ b/protocol/jsonrpc/jsonrpc_exporter.go @@ -28,16 +28,19 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// JsonrpcExporter ... type JsonrpcExporter struct { protocol.BaseExporter } +// NewJsonrpcExporter ... func NewJsonrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *JsonrpcExporter { return &JsonrpcExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } +// Unexport ... func (je *JsonrpcExporter) Unexport() { serviceId := je.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") je.BaseExporter.Unexport() diff --git a/protocol/jsonrpc/jsonrpc_invoker.go b/protocol/jsonrpc/jsonrpc_invoker.go index 2c130e0d7617e96a1724edc5b63f8e66f251446e..b6e194ce0e93e84c164eccf8574e5eb20430f6e8 100644 --- a/protocol/jsonrpc/jsonrpc_invoker.go +++ b/protocol/jsonrpc/jsonrpc_invoker.go @@ -29,11 +29,13 @@ import ( invocation_impl "github.com/apache/dubbo-go/protocol/invocation" ) +// JsonrpcInvoker ... type JsonrpcInvoker struct { protocol.BaseInvoker client *HTTPClient } +// NewJsonrpcInvoker ... func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { return &JsonrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -41,7 +43,8 @@ func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { } } -func (ji *JsonrpcInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +// Invoke ... +func (ji *JsonrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( result protocol.RPCResult @@ -50,12 +53,12 @@ func (ji *JsonrpcInvoker) Invoke(invocation protocol.Invocation) protocol.Result inv := invocation.(*invocation_impl.RPCInvocation) url := ji.GetUrl() req := ji.client.NewRequest(url, inv.MethodName(), inv.Arguments()) - ctx := context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ + ctxNew := context.WithValue(ctx, constant.DUBBOGO_CTX_KEY, map[string]string{ "X-Proxy-Id": "dubbogo", "X-Services": url.Path, "X-Method": inv.MethodName(), }) - result.Err = ji.client.Call(ctx, url, req, inv.Reply()) + result.Err = ji.client.Call(ctxNew, url, req, inv.Reply()) if result.Err == nil { result.Rest = inv.Reply() } diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index 8c910339858f4960ad0e394ae6271863d7654adc..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{ @@ -60,7 +60,7 @@ func TestJsonrpcInvoker_Invoke(t *testing.T) { jsonInvoker := NewJsonrpcInvoker(url, client) user := &User{} - res := jsonInvoker.Invoke(invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), invocation.WithArguments([]interface{}{"1", "username"}), + res := jsonInvoker.Invoke(context.Background(), invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), invocation.WithArguments([]interface{}{"1", "username"}), invocation.WithReply(user))) assert.NoError(t, res.Error()) diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go index c18345d413edb2d263f1acaef1741514b665f042..bed7099ab60a6c05c3799f993c0bb348a4b00f02 100644 --- a/protocol/jsonrpc/jsonrpc_protocol.go +++ b/protocol/jsonrpc/jsonrpc_protocol.go @@ -20,17 +20,23 @@ package jsonrpc import ( "strings" "sync" + "time" ) import ( "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/config" "github.com/apache/dubbo-go/protocol" ) -const JSONRPC = "jsonrpc" +const ( + // JSONRPC + //module name + JSONRPC = "jsonrpc" +) func init() { extension.SetProtocol(JSONRPC, GetProtocol) @@ -38,12 +44,14 @@ func init() { var jsonrpcProtocol *JsonrpcProtocol +// JsonrpcProtocol ... type JsonrpcProtocol struct { protocol.BaseProtocol serverMap map[string]*Server serverLock sync.Mutex } +// NewJsonrpcProtocol ... func NewJsonrpcProtocol() *JsonrpcProtocol { return &JsonrpcProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -51,6 +59,7 @@ func NewJsonrpcProtocol() *JsonrpcProtocol { } } +// Export ... func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := strings.TrimPrefix(url.Path, "/") @@ -65,16 +74,26 @@ func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } +// Refer ... func (jp *JsonrpcProtocol) Refer(url common.URL) protocol.Invoker { + //default requestTimeout + var requestTimeout = config.GetConsumerConfig().RequestTimeout + + requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) + if t, err := time.ParseDuration(requestTimeoutStr); err == nil { + requestTimeout = t + } + invoker := NewJsonrpcInvoker(url, NewHTTPClient(&HTTPOptions{ HandshakeTimeout: config.GetConsumerConfig().ConnectTimeout, - HTTPTimeout: config.GetConsumerConfig().RequestTimeout, + HTTPTimeout: requestTimeout, })) jp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) return invoker } +// Destroy ... func (jp *JsonrpcProtocol) Destroy() { logger.Infof("jsonrpcProtocol destroy.") @@ -106,6 +125,7 @@ func (jp *JsonrpcProtocol) openServer(url common.URL) { } } +// GetProtocol ... func GetProtocol() protocol.Protocol { if jsonrpcProtocol == nil { jsonrpcProtocol = NewJsonrpcProtocol() 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/jsonrpc/server.go b/protocol/jsonrpc/server.go index dc85e0f5e76fd07dbcd11646ae529c98e5323a15..8600f02dad3d32d797613823de0bbe40261d2e71 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -32,6 +32,7 @@ import ( ) import ( + "github.com/opentracing/opentracing-go" perrors "github.com/pkg/errors" ) @@ -50,11 +51,15 @@ var ( ) const ( - DefaultMaxSleepTime = 1 * time.Second // accept涓棿鏈€澶leep interval + // DefaultMaxSleepTime max sleep interval in accept + DefaultMaxSleepTime = 1 * time.Second + // DefaultHTTPRspBufferSize ... DefaultHTTPRspBufferSize = 1024 - PathPrefix = byte('/') + // PathPrefix ... + PathPrefix = byte('/') ) +// Server ... type Server struct { done chan struct{} once sync.Once @@ -64,6 +69,7 @@ type Server struct { timeout time.Duration } +// NewServer ... func NewServer() *Server { return &Server{ done: make(chan struct{}), @@ -93,6 +99,8 @@ func (s *Server) handlePkg(conn net.Conn) { rsp := &http.Response{ Header: header, StatusCode: 500, + ProtoMajor: 1, + ProtoMinor: 1, ContentLength: int64(len(body)), Body: ioutil.NopCloser(bytes.NewReader(body)), } @@ -147,6 +155,13 @@ func (s *Server) handlePkg(conn net.Conn) { } ctx := context.Background() + + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, + opentracing.HTTPHeadersCarrier(r.Header)) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + if len(reqHeader["Timeout"]) > 0 { timeout, err := time.ParseDuration(reqHeader["Timeout"]) if err == nil { @@ -213,6 +228,7 @@ func accept(listener net.Listener, fn func(net.Conn)) error { } } +// Start ... func (s *Server) Start(url common.URL) { listener, err := net.Listen("tcp", url.Location) if err != nil { @@ -239,6 +255,7 @@ func (s *Server) Start(url common.URL) { }() } +// Stop ... func (s *Server) Stop() { s.once.Do(func() { close(s.done) @@ -252,6 +269,8 @@ func serveRequest(ctx context.Context, rsp := &http.Response{ Header: make(http.Header), StatusCode: 500, + ProtoMajor: 1, + ProtoMinor: 1, ContentLength: int64(len(body)), Body: ioutil.NopCloser(bytes.NewReader(body)), } @@ -276,6 +295,8 @@ func serveRequest(ctx context.Context, rsp := &http.Response{ Header: make(http.Header), StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, ContentLength: int64(len(body)), Body: ioutil.NopCloser(bytes.NewReader(body)), } @@ -324,10 +345,9 @@ func serveRequest(ctx context.Context, exporter, _ := jsonrpcProtocol.ExporterMap().Load(path) invoker := exporter.(*JsonrpcExporter).GetInvoker() if invoker != nil { - result := invoker.Invoke(invocation.NewRPCInvocation(methodName, args, map[string]string{ + result := invoker.Invoke(ctx, invocation.NewRPCInvocation(methodName, args, map[string]string{ constant.PATH_KEY: path, - constant.VERSION_KEY: codec.req.Version, - })) + constant.VERSION_KEY: codec.req.Version})) if err := result.Error(); err != nil { rspStream, err := codec.Write(err.Error(), invalidRequest) if err != nil { diff --git a/protocol/mock/mock_invoker.go b/protocol/mock/mock_invoker.go index c509cef054f5a23fe504486e01d7cc0e8772711d..5c5b476b7b07f6c41a74a7ec8f51648aff84b1a3 100644 --- a/protocol/mock/mock_invoker.go +++ b/protocol/mock/mock_invoker.go @@ -21,6 +21,7 @@ package mock import ( + "context" "reflect" ) @@ -91,7 +92,7 @@ func (mr *MockInvokerMockRecorder) Destroy() *gomock.Call { } // Invoke mocks base method -func (m *MockInvoker) Invoke(arg0 protocol.Invocation) protocol.Result { +func (m *MockInvoker) Invoke(ctx context.Context, arg0 protocol.Invocation) protocol.Result { ret := m.ctrl.Call(m, "Invoke", arg0) ret0, _ := ret[0].(protocol.Result) return ret0 diff --git a/protocol/protocol.go b/protocol/protocol.go index 814a85163a99aa3b161b5eafbfed5f13ac4e3eb4..a873469a8ba361c9dfc922b071ffbf256c6a8b98 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -26,6 +26,7 @@ import ( "github.com/apache/dubbo-go/common/logger" ) +// Protocol // Extension - protocol type Protocol interface { Export(invoker Invoker) Exporter @@ -33,6 +34,7 @@ type Protocol interface { Destroy() } +// Exporter // wrapping invoker type Exporter interface { GetInvoker() Invoker @@ -43,37 +45,45 @@ type Exporter interface { // base protocol ///////////////////////////// +// BaseProtocol ... type BaseProtocol struct { exporterMap *sync.Map invokers []Invoker } +// NewBaseProtocol ... func NewBaseProtocol() BaseProtocol { return BaseProtocol{ exporterMap: new(sync.Map), } } +// SetExporterMap ... func (bp *BaseProtocol) SetExporterMap(key string, exporter Exporter) { bp.exporterMap.Store(key, exporter) } +// ExporterMap ... func (bp *BaseProtocol) ExporterMap() *sync.Map { return bp.exporterMap } +// SetInvokers ... func (bp *BaseProtocol) SetInvokers(invoker Invoker) { bp.invokers = append(bp.invokers, invoker) } +// Invokers ... func (bp *BaseProtocol) Invokers() []Invoker { return bp.invokers } +// Export ... func (bp *BaseProtocol) Export(invoker Invoker) Exporter { return NewBaseExporter("base", invoker, bp.exporterMap) } +// Refer ... func (bp *BaseProtocol) Refer(url common.URL) Invoker { return NewBaseInvoker(url) } @@ -103,12 +113,14 @@ func (bp *BaseProtocol) Destroy() { // base exporter ///////////////////////////// +// BaseExporter ... type BaseExporter struct { key string invoker Invoker exporterMap *sync.Map } +// NewBaseExporter ... func NewBaseExporter(key string, invoker Invoker, exporterMap *sync.Map) *BaseExporter { return &BaseExporter{ key: key, @@ -117,11 +129,13 @@ func NewBaseExporter(key string, invoker Invoker, exporterMap *sync.Map) *BaseEx } } +// GetInvoker ... func (de *BaseExporter) GetInvoker() Invoker { return de.invoker } +// Unexport ... func (de *BaseExporter) Unexport() { logger.Infof("Exporter unexport.") de.invoker.Destroy() diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go index 2efc34da4469cf369d4bbeb871ccfbdb73123f6a..dedf8aa64b6ae1b7b4782350e2625b02171aac44 100644 --- a/protocol/protocolwrapper/mock_protocol_filter.go +++ b/protocol/protocolwrapper/mock_protocol_filter.go @@ -28,6 +28,7 @@ import ( type mockProtocolFilter struct{} +// NewMockProtocolFilter ... func NewMockProtocolFilter() protocol.Protocol { return &mockProtocolFilter{} } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index 7c58fabea3cccf5a39e1622fedd4a3a297e05983..70d2da0faed3bc9797eb23cec653bea05d445d91 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -18,6 +18,7 @@ package protocolwrapper import ( + "context" "strings" ) @@ -30,6 +31,7 @@ import ( ) const ( + // FILTER ... FILTER = "filter" ) @@ -37,11 +39,13 @@ func init() { extension.SetProtocol(FILTER, GetProtocol) } +// ProtocolFilterWrapper // protocol in url decide who ProtocolFilterWrapper.protocol is type ProtocolFilterWrapper struct { protocol protocol.Protocol } +// Export ... func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Exporter { if pfw.protocol == nil { pfw.protocol = extension.GetProtocol(invoker.GetUrl().Protocol) @@ -50,6 +54,7 @@ func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Expo return pfw.protocol.Export(invoker) } +// Refer ... func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker { if pfw.protocol == nil { pfw.protocol = extension.GetProtocol(url.Protocol) @@ -57,6 +62,7 @@ func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker { return buildInvokerChain(pfw.protocol.Refer(url), constant.REFERENCE_FILTER_KEY) } +// Destroy ... func (pfw *ProtocolFilterWrapper) Destroy() { pfw.protocol.Destroy() } @@ -72,14 +78,15 @@ func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { // The order of filters is from left to right, so loading from right to left for i := len(filtNames) - 1; i >= 0; i-- { - filter := extension.GetFilter(filtNames[i]) - fi := &FilterInvoker{next: next, invoker: invoker, filter: filter} + flt := extension.GetFilter(filtNames[i]) + fi := &FilterInvoker{next: next, invoker: invoker, filter: flt} next = fi } return next } +// GetProtocol ... func GetProtocol() protocol.Protocol { return &ProtocolFilterWrapper{} } @@ -88,25 +95,30 @@ func GetProtocol() protocol.Protocol { // filter invoker /////////////////////////// +// FilterInvoker ... type FilterInvoker struct { next protocol.Invoker invoker protocol.Invoker filter filter.Filter } +// GetUrl ... func (fi *FilterInvoker) GetUrl() common.URL { return fi.invoker.GetUrl() } +// IsAvailable ... func (fi *FilterInvoker) IsAvailable() bool { return fi.invoker.IsAvailable() } -func (fi *FilterInvoker) Invoke(invocation protocol.Invocation) protocol.Result { - result := fi.filter.Invoke(fi.next, invocation) - return fi.filter.OnResponse(result, fi.invoker, invocation) +// Invoke ... +func (fi *FilterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + result := fi.filter.Invoke(ctx, fi.next, invocation) + return fi.filter.OnResponse(ctx, result, fi.invoker, invocation) } +// Destroy ... func (fi *FilterInvoker) Destroy() { fi.invoker.Destroy() } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go index dc376313549c24da1cc6cb64a42e8445ef4fe346..8491d57462d47d6af72040d41b78dcb30e6da697 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go @@ -18,6 +18,7 @@ package protocolwrapper import ( + "context" "net/url" "testing" ) @@ -66,7 +67,7 @@ func init() { type EchoFilterForTest struct{} -func (ef *EchoFilterForTest) Invoke(invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +func (ef *EchoFilterForTest) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking echo filter.") logger.Debugf("%v,%v", invocation.MethodName(), len(invocation.Arguments())) if invocation.MethodName() == constant.ECHO && len(invocation.Arguments()) == 1 { @@ -75,10 +76,10 @@ func (ef *EchoFilterForTest) Invoke(invoker protocol.Invoker, invocation protoco } } - return invoker.Invoke(invocation) + return invoker.Invoke(ctx, invocation) } -func (ef *EchoFilterForTest) OnResponse(result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { +func (ef *EchoFilterForTest) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } diff --git a/protocol/result.go b/protocol/result.go index dcdb62310d359d441067395ea92f8460df97eb22..34e76d2dddbaed33b2e2c015631443565cfaea87 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -17,6 +17,7 @@ package protocol +// Result ... type Result interface { SetError(error) Error() error @@ -32,12 +33,14 @@ type Result interface { // Result Impletment of RPC ///////////////////////////// +// RPCResult ... type RPCResult struct { Attrs map[string]string Err error Rest interface{} } +// SetError ... func (r *RPCResult) SetError(err error) { r.Err = err } @@ -46,26 +49,32 @@ func (r *RPCResult) Error() error { return r.Err } +// SetResult ... func (r *RPCResult) SetResult(rest interface{}) { r.Rest = rest } +// Result ... func (r *RPCResult) Result() interface{} { return r.Rest } +// SetAttachments ... func (r *RPCResult) SetAttachments(attr map[string]string) { r.Attrs = attr } +// Attachments ... func (r *RPCResult) Attachments() map[string]string { return r.Attrs } +// AddAttachment ... func (r *RPCResult) AddAttachment(key, value string) { r.Attrs[key] = value } +// Attachment ... func (r *RPCResult) Attachment(key, defaultValue string) string { v, ok := r.Attrs[key] if !ok { diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 3a8bfbc87f285e0e86269d44c47d6771566d97b1..13be47c98ece1cc006250ad49ab2b9a8c3b1f625 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -20,6 +20,7 @@ package protocol import ( "sync" "sync/atomic" + "time" ) import ( @@ -27,18 +28,82 @@ import ( ) var ( - methodStatistics sync.Map // url -> { methodName : RpcStatus} + methodStatistics sync.Map // url -> { methodName : RPCStatus} + serviceStatistic sync.Map // url -> RPCStatus ) -type RpcStatus struct { - active int32 +// RPCStatus ... +type RPCStatus struct { + active int32 + failed int32 + total int32 + totalElapsed int64 + failedElapsed int64 + maxElapsed int64 + failedMaxElapsed int64 + succeededMaxElapsed int64 + successiveRequestFailureCount int32 + lastRequestFailedTimestamp int64 } -func (rpc *RpcStatus) GetActive() int32 { +// GetActive ... +func (rpc *RPCStatus) GetActive() int32 { return atomic.LoadInt32(&rpc.active) } -func GetStatus(url common.URL, methodName string) *RpcStatus { +// GetFailed ... +func (rpc *RPCStatus) GetFailed() int32 { + return atomic.LoadInt32(&rpc.failed) +} + +// GetTotal ... +func (rpc *RPCStatus) GetTotal() int32 { + return atomic.LoadInt32(&rpc.total) +} + +// GetTotalElapsed ... +func (rpc *RPCStatus) GetTotalElapsed() int64 { + return atomic.LoadInt64(&rpc.totalElapsed) +} + +// GetFailedElapsed ... +func (rpc *RPCStatus) GetFailedElapsed() int64 { + return atomic.LoadInt64(&rpc.failedElapsed) +} + +// GetMaxElapsed ... +func (rpc *RPCStatus) GetMaxElapsed() int64 { + return atomic.LoadInt64(&rpc.maxElapsed) +} + +// GetFailedMaxElapsed ... +func (rpc *RPCStatus) GetFailedMaxElapsed() int64 { + return atomic.LoadInt64(&rpc.failedMaxElapsed) +} + +// GetSucceededMaxElapsed ... +func (rpc *RPCStatus) GetSucceededMaxElapsed() int64 { + return atomic.LoadInt64(&rpc.succeededMaxElapsed) +} + +// GetLastRequestFailedTimestamp ... +func (rpc *RPCStatus) GetLastRequestFailedTimestamp() int64 { + return atomic.LoadInt64(&rpc.lastRequestFailedTimestamp) +} + +// GetSuccessiveRequestFailureCount ... +func (rpc *RPCStatus) GetSuccessiveRequestFailureCount() int32 { + return atomic.LoadInt32(&rpc.successiveRequestFailureCount) +} + +// GetURLStatus ... +func GetURLStatus(url common.URL) *RPCStatus { + rpcStatus, _ := serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) + return rpcStatus.(*RPCStatus) +} + +// GetMethodStatus ... +func GetMethodStatus(url common.URL, methodName string) *RPCStatus { identifier := url.Key() methodMap, found := methodStatistics.Load(identifier) if !found { @@ -49,27 +114,70 @@ func GetStatus(url common.URL, methodName string) *RpcStatus { methodActive := methodMap.(*sync.Map) rpcStatus, found := methodActive.Load(methodName) if !found { - rpcStatus = &RpcStatus{} + rpcStatus = &RPCStatus{} methodActive.Store(methodName, rpcStatus) } - status := rpcStatus.(*RpcStatus) + status := rpcStatus.(*RPCStatus) return status } +// BeginCount ... func BeginCount(url common.URL, methodName string) { - beginCount0(GetStatus(url, methodName)) + beginCount0(GetURLStatus(url)) + beginCount0(GetMethodStatus(url, methodName)) } -func EndCount(url common.URL, methodName string) { - endCount0(GetStatus(url, methodName)) +// EndCount ... +func EndCount(url common.URL, methodName string, elapsed int64, succeeded bool) { + endCount0(GetURLStatus(url), elapsed, succeeded) + endCount0(GetMethodStatus(url, methodName), elapsed, succeeded) } // private methods -func beginCount0(rpcStatus *RpcStatus) { +func beginCount0(rpcStatus *RPCStatus) { atomic.AddInt32(&rpcStatus.active, 1) } -func endCount0(rpcStatus *RpcStatus) { +func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { atomic.AddInt32(&rpcStatus.active, -1) + atomic.AddInt32(&rpcStatus.total, 1) + atomic.AddInt64(&rpcStatus.totalElapsed, elapsed) + + if rpcStatus.maxElapsed < elapsed { + atomic.StoreInt64(&rpcStatus.maxElapsed, elapsed) + } + if succeeded { + if rpcStatus.succeededMaxElapsed < elapsed { + atomic.StoreInt64(&rpcStatus.succeededMaxElapsed, elapsed) + } + atomic.StoreInt32(&rpcStatus.successiveRequestFailureCount, 0) + } else { + atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, CurrentTimeMillis()) + atomic.AddInt32(&rpcStatus.successiveRequestFailureCount, 1) + atomic.AddInt32(&rpcStatus.failed, 1) + atomic.AddInt64(&rpcStatus.failedElapsed, elapsed) + if rpcStatus.failedMaxElapsed < elapsed { + atomic.StoreInt64(&rpcStatus.failedMaxElapsed, elapsed) + } + } +} + +// CurrentTimeMillis ... +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 new file mode 100644 index 0000000000000000000000000000000000000000..5a07f44eab0db60ba65a155d6c6190ab4ce2d716 --- /dev/null +++ b/protocol/rpc_status_test.go @@ -0,0 +1,138 @@ +package protocol + +import ( + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +func TestBeginCount(t *testing.T) { + defer CleanAllStatus() + + url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + BeginCount(url, "test") + urlStatus := GetURLStatus(url) + methodStatus := GetMethodStatus(url, "test") + methodStatus1 := GetMethodStatus(url, "test1") + assert.Equal(t, int32(1), methodStatus.active) + assert.Equal(t, int32(1), urlStatus.active) + assert.Equal(t, int32(0), methodStatus1.active) + +} + +func TestEndCount(t *testing.T) { + defer CleanAllStatus() + + 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") + assert.Equal(t, int32(-1), methodStatus.active) + assert.Equal(t, int32(-1), urlStatus.active) + assert.Equal(t, int32(1), methodStatus.total) + assert.Equal(t, int32(1), urlStatus.total) +} + +func TestGetMethodStatus(t *testing.T) { + defer CleanAllStatus() + + 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 CleanAllStatus() + + 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 CleanAllStatus() + + 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 CleanAllStatus() + + 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") + assert.Equal(t, int32(1), methodStatus.total) + assert.Equal(t, int32(1), urlStatus.total) + assert.Equal(t, int32(0), methodStatus.active) + assert.Equal(t, int32(0), urlStatus.active) + assert.Equal(t, int32(0), methodStatus.failed) + assert.Equal(t, int32(0), urlStatus.failed) + assert.Equal(t, int32(0), methodStatus.successiveRequestFailureCount) + assert.Equal(t, int32(0), urlStatus.successiveRequestFailureCount) + assert.Equal(t, int64(100), methodStatus.totalElapsed) + assert.Equal(t, int64(100), urlStatus.totalElapsed) + request(url, "test", 100, false, false) + request(url, "test", 100, false, false) + request(url, "test", 100, false, false) + request(url, "test", 100, false, false) + request(url, "test", 100, false, false) + assert.Equal(t, int32(6), methodStatus.total) + assert.Equal(t, int32(6), urlStatus.total) + assert.Equal(t, int32(5), methodStatus.failed) + assert.Equal(t, int32(5), urlStatus.failed) + assert.Equal(t, int32(5), methodStatus.successiveRequestFailureCount) + assert.Equal(t, int32(5), urlStatus.successiveRequestFailureCount) + assert.Equal(t, int64(600), methodStatus.totalElapsed) + assert.Equal(t, int64(600), urlStatus.totalElapsed) + assert.Equal(t, int64(500), methodStatus.failedElapsed) + assert.Equal(t, int64(500), urlStatus.failedElapsed) + + request(url, "test", 100, false, true) + assert.Equal(t, int32(0), methodStatus.successiveRequestFailureCount) + assert.Equal(t, int32(0), urlStatus.successiveRequestFailureCount) + + request(url, "test", 200, false, false) + request(url, "test", 200, false, false) + assert.Equal(t, int32(2), methodStatus.successiveRequestFailureCount) + assert.Equal(t, int32(2), urlStatus.successiveRequestFailureCount) + assert.Equal(t, int64(200), methodStatus.maxElapsed) + assert.Equal(t, int64(200), urlStatus.maxElapsed) + + request(url, "test1", 200, false, false) + request(url, "test1", 200, false, false) + request(url, "test1", 200, false, false) + assert.Equal(t, int32(5), urlStatus.successiveRequestFailureCount) + methodStatus1 := GetMethodStatus(url, "test1") + assert.Equal(t, int32(2), methodStatus.successiveRequestFailureCount) + assert.Equal(t, int32(3), methodStatus1.successiveRequestFailureCount) + +} + +func request(url common.URL, method string, elapsed int64, active, succeeded bool) { + BeginCount(url, method) + if !active { + EndCount(url, method, elapsed, succeeded) + } +} + +func TestCurrentTimeMillis(t *testing.T) { + defer CleanAllStatus() + c := CurrentTimeMillis() + assert.NotNil(t, c) + str := strconv.FormatInt(c, 10) + i, _ := strconv.ParseInt(str, 10, 64) + assert.Equal(t, c, i) +} diff --git a/registry/base_configuration_listener.go b/registry/base_configuration_listener.go index 925baa2198d9917824c1be78b7cd0c2f93bfb894..55418318dfc52ed9f17f1ec6a18ad9ef9d8163bf 100644 --- a/registry/base_configuration_listener.go +++ b/registry/base_configuration_listener.go @@ -29,15 +29,19 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// BaseConfigurationListener ... type BaseConfigurationListener struct { configurators []config_center.Configurator dynamicConfiguration config_center.DynamicConfiguration defaultConfiguratorFunc func(url *common.URL) config_center.Configurator } +// Configurators ... func (bcl *BaseConfigurationListener) Configurators() []config_center.Configurator { return bcl.configurators } + +// InitWith ... func (bcl *BaseConfigurationListener) InitWith(key string, listener config_center.ConfigurationListener, f func(url *common.URL) config_center.Configurator) { bcl.dynamicConfiguration = config.GetEnvInstance().GetDynamicConfiguration() if bcl.dynamicConfiguration == nil { @@ -47,7 +51,7 @@ func (bcl *BaseConfigurationListener) InitWith(key string, listener config_cente } bcl.defaultConfiguratorFunc = f bcl.dynamicConfiguration.AddListener(key, listener) - if rawConfig, err := bcl.dynamicConfiguration.GetConfig(key, config_center.WithGroup(constant.DUBBO)); err != nil { + if rawConfig, err := bcl.dynamicConfiguration.GetInternalProperty(key, config_center.WithGroup(constant.DUBBO)); err != nil { //set configurators to empty bcl.configurators = []config_center.Configurator{} return @@ -56,6 +60,7 @@ func (bcl *BaseConfigurationListener) InitWith(key string, listener config_cente } } +// Process ... func (bcl *BaseConfigurationListener) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of overriding rule, change type is: %v , raw config content is:%v", event.ConfigType, event.Value) if event.ConfigType == remoting.EventTypeDel { @@ -76,12 +81,15 @@ func (bcl *BaseConfigurationListener) genConfiguratorFromRawRule(rawConfig strin bcl.configurators = ToConfigurators(urls, bcl.defaultConfiguratorFunc) return nil } + +// OverrideUrl ... func (bcl *BaseConfigurationListener) OverrideUrl(url *common.URL) { for _, v := range bcl.configurators { v.Configure(url) } } +// ToConfigurators ... func ToConfigurators(urls []*common.URL, f func(url *common.URL) config_center.Configurator) []config_center.Configurator { if len(urls) == 0 { return nil diff --git a/registry/base_registry.go b/registry/base_registry.go new file mode 100644 index 0000000000000000000000000000000000000000..5b9aef82928d491d4b8f4dbe3caa4bd64a185dad --- /dev/null +++ b/registry/base_registry.go @@ -0,0 +1,375 @@ +/* + * 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 registry + +import ( + "context" + "fmt" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" +) + +import ( + gxnet "github.com/dubbogo/gost/net" + 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" +) + +const ( + // RegistryConnDelay connection delay + RegistryConnDelay = 3 + // MaxWaitInterval max wait interval + MaxWaitInterval = 3 * time.Second +) + +var ( + processID = "" + localIP = "" +) + +func init() { + processID = fmt.Sprintf("%d", os.Getpid()) + localIP, _ = gxnet.GetLocalIP() +} + +/* + * -----------------------------------NOTICE--------------------------------------------- + * If there is no special case, you'd better inherit BaseRegistry and implement the + * FacadeBasedRegistry interface instead of directly implementing the Registry interface. + * -------------------------------------------------------------------------------------- + */ + +/* + * FacadeBasedRegistry interface is subclass of Registry, and it is designed for registry who want to inherit BaseRegistry. + * You have to implement the interface to inherit BaseRegistry. + */ +type FacadeBasedRegistry interface { + Registry + CreatePath(string) error + DoRegister(string, string) error + DoSubscribe(conf *common.URL) (Listener, error) + CloseAndNilClient() + CloseListener() + InitListeners() +} + +// BaseRegistry is a common logic abstract for registry. It implement Registry interface. +type BaseRegistry struct { + context context.Context + facadeBasedRegistry FacadeBasedRegistry + *common.URL + birth int64 // time of file birth, seconds since Epoch; 0 if unknown + wg sync.WaitGroup // wg+done for zk restart + done chan struct{} + cltLock sync.Mutex //ctl lock is a lock for services map + services map[string]common.URL // service name + protocol -> service config, for store the service registered +} + +// InitBaseRegistry for init some local variables and set BaseRegistry's subclass to it +func (r *BaseRegistry) InitBaseRegistry(url *common.URL, facadeRegistry FacadeBasedRegistry) Registry { + r.URL = url + r.birth = time.Now().UnixNano() + r.done = make(chan struct{}) + r.services = make(map[string]common.URL) + r.facadeBasedRegistry = facadeRegistry + return r +} + +// GetUrl for get registry's url +func (r *BaseRegistry) GetUrl() common.URL { + return *r.URL +} + +// Destroy for graceful down +func (r *BaseRegistry) Destroy() { + //first step close registry's all listeners + r.facadeBasedRegistry.CloseListener() + // then close r.done to notify other program who listen to it + close(r.done) + // wait waitgroup done (wait listeners outside close over) + r.wg.Wait() + //close registry client + r.closeRegisters() +} + +// Register implement interface registry to register +func (r *BaseRegistry) Register(conf common.URL) error { + var ( + ok bool + err error + ) + role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) + // Check if the service has been registered + r.cltLock.Lock() + _, ok = r.services[conf.Key()] + r.cltLock.Unlock() + if ok { + return perrors.Errorf("Path{%s} has been registered", conf.Key()) + } + + err = r.register(conf) + if err != nil { + return perrors.WithMessagef(err, "register(conf:%+v)", conf) + } + + r.cltLock.Lock() + r.services[conf.Key()] = conf + r.cltLock.Unlock() + logger.Debugf("(%sRegistry)Register(conf{%#v})", common.DubboRole[role], conf) + + return nil +} + +// service is for getting service path stored in url +func (r *BaseRegistry) service(c common.URL) string { + return url.QueryEscape(c.Service()) +} + +// RestartCallBack for reregister when reconnect +func (r *BaseRegistry) RestartCallBack() bool { + + // copy r.services + services := []common.URL{} + for _, confIf := range r.services { + services = append(services, confIf) + } + + flag := true + for _, confIf := range services { + err := r.register(confIf) + if err != nil { + logger.Errorf("(ZkProviderRegistry)register(conf{%#v}) = error{%#v}", + confIf, perrors.WithStack(err)) + flag = false + break + } + logger.Infof("success to re-register service :%v", confIf.Key()) + } + r.facadeBasedRegistry.InitListeners() + + return flag +} + +// register for register url to registry, include init params +func (r *BaseRegistry) register(c common.URL) error { + var ( + err error + //revision string + params url.Values + rawURL string + encodedURL string + dubboPath string + //conf config.URL + ) + params = url.Values{} + + c.RangeParams(func(key, value string) bool { + params.Add(key, value) + return true + }) + + params.Add("pid", processID) + params.Add("ip", localIP) + //params.Add("timeout", fmt.Sprintf("%d", int64(r.Timeout)/1e6)) + + role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) + switch role { + + case common.PROVIDER: + dubboPath, rawURL, err = r.providerRegistry(c, params) + case common.CONSUMER: + dubboPath, rawURL, err = r.consumerRegistry(c, params) + default: + return perrors.Errorf("@c{%v} type is not referencer or provider", c) + } + encodedURL = url.QueryEscape(rawURL) + dubboPath = strings.ReplaceAll(dubboPath, "$", "%24") + err = r.facadeBasedRegistry.DoRegister(dubboPath, encodedURL) + + if err != nil { + return perrors.WithMessagef(err, "register Node(path:%s, url:%s)", dubboPath, rawURL) + } + return nil +} + +// providerRegistry for provider role do +func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string, string, error) { + var ( + dubboPath string + rawURL string + err error + ) + if c.Path == "" || len(c.Methods) == 0 { + return "", "", perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods) + } + dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) + r.cltLock.Lock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + r.cltLock.Unlock() + if err != nil { + logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%#v}", dubboPath, perrors.WithStack(err)) + return "", "", perrors.WithMessagef(err, "facadeBasedRegistry.CreatePath(path:%s)", dubboPath) + } + params.Add("anyhost", "true") + + // Dubbo java consumer to start looking for the provider url,because the category does not match, + // the provider will not find, causing the consumer can not start, so we use consumers. + // DubboRole = [...]string{"consumer", "", "", "provider"} + // params.Add("category", (RoleType(PROVIDER)).Role()) + params.Add("category", (common.RoleType(common.PROVIDER)).String()) + params.Add("dubbo", "dubbo-provider-golang-"+constant.Version) + + params.Add("side", (common.RoleType(common.PROVIDER)).Role()) + + if len(c.Methods) == 0 { + params.Add("methods", strings.Join(c.Methods, ",")) + } + logger.Debugf("provider url params:%#v", params) + var host string + if c.Ip == "" { + host = localIP + ":" + c.Port + } else { + host = c.Ip + ":" + c.Port + } + + rawURL = fmt.Sprintf("%s://%s%s?%s", c.Protocol, host, c.Path, params.Encode()) + // Print your own registration service providers. + dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.PROVIDER)).String()) + logger.Debugf("provider path:%s, url:%s", dubboPath, rawURL) + return dubboPath, rawURL, nil +} + +// consumerRegistry for consumer role do +func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string, string, error) { + var ( + dubboPath string + rawURL string + err error + ) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.CONSUMER]) + r.cltLock.Lock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + r.cltLock.Unlock() + if err != nil { + logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) + return "", "", perrors.WithStack(err) + } + dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) + r.cltLock.Lock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + r.cltLock.Unlock() + if err != nil { + logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) + return "", "", perrors.WithStack(err) + } + + params.Add("protocol", c.Protocol) + params.Add("category", (common.RoleType(common.CONSUMER)).String()) + params.Add("dubbo", "dubbogo-consumer-"+constant.Version) + + rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode()) + dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.CONSUMER)).String()) + + logger.Debugf("consumer path:%s, url:%s", dubboPath, rawURL) + return dubboPath, rawURL, nil +} + +// sleepWait... +func sleepWait(n int) { + wait := time.Duration((n + 1) * 2e8) + if wait > MaxWaitInterval { + wait = MaxWaitInterval + } + time.Sleep(wait) +} + +// Subscribe :subscribe from registry, event will notify by notifyListener +func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) { + n := 0 + for { + n++ + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + return + } + + listener, err := r.facadeBasedRegistry.DoSubscribe(url) + if err != nil { + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + return + } + logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) + time.Sleep(time.Duration(RegistryConnDelay) * time.Second) + continue + } + + for { + if serviceEvent, err := listener.Next(); err != nil { + logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) + listener.Close() + break + } else { + logger.Infof("update begin, service event: %v", serviceEvent.String()) + notifyListener.Notify(serviceEvent) + } + + } + sleepWait(n) + } +} + +// closeRegisters close and remove registry client and reset services map +func (r *BaseRegistry) closeRegisters() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + logger.Infof("begin to close provider client") + // Close and remove(set to nil) the registry client + r.facadeBasedRegistry.CloseAndNilClient() + // reset the services map + r.services = nil +} + +// IsAvailable judge to is registry not closed by chan r.done +func (r *BaseRegistry) IsAvailable() bool { + select { + case <-r.done: + return false + default: + return true + } +} + +// WaitGroup open for outside add the waitgroup to add some logic before registry destroyed over(graceful down) +func (r *BaseRegistry) WaitGroup() *sync.WaitGroup { + return &r.wg +} + +// Done open for outside to listen the event of registry Destroy() called. +func (r *BaseRegistry) Done() chan struct{} { + return r.done +} diff --git a/registry/consul/registry.go b/registry/consul/registry.go index 73bf3975bc7c73f4a7748f46280ffb1aa5525ca8..c5b8510a6c87068a5b4f1ce52203d401a896a6c2 100644 --- a/registry/consul/registry.go +++ b/registry/consul/registry.go @@ -36,6 +36,7 @@ import ( ) const ( + // RegistryConnDelay ... RegistryConnDelay = 3 ) @@ -137,14 +138,15 @@ func (r *consulRegistry) subscribe(url *common.URL, notifyListener registry.Noti } for { - if serviceEvent, err := listener.Next(); err != nil { + serviceEvent, err := listener.Next() + if err != nil { logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) listener.Close() return - } else { - logger.Infof("update begin, service event: %v", serviceEvent.String()) - notifyListener.Notify(serviceEvent) } + + logger.Infof("update begin, service event: %v", serviceEvent.String()) + notifyListener.Notify(serviceEvent) } } } 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 e88c611f6f20bc182c3630e328caab848affc08b..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 ( @@ -41,10 +42,12 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// Options ... type Options struct { serviceTTL time.Duration } +// Option ... type Option func(*Options) type registryDirectory struct { @@ -59,8 +62,11 @@ type registryDirectory struct { consumerConfigurationListener *consumerConfigurationListener referenceConfigurationListener *referenceConfigurationListener Options + serviceKey string + forbidden atomic.Bool } +// NewRegistryDirectory ... func NewRegistryDirectory(url *common.URL, registry registry.Registry, opts ...Option) (*registryDirectory, error) { options := Options{ //default 300s @@ -106,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 @@ -118,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 @@ -135,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 { @@ -167,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)) } } @@ -174,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 @@ -190,55 +221,63 @@ 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 { if !dir.BaseDirectory.IsAvailable() { return dir.BaseDirectory.IsAvailable() - } else { - for _, ivk := range dir.cacheInvokers { - if ivk.IsAvailable() { - return true - } + } + + for _, ivk := range dir.cacheInvokers { + if ivk.IsAvailable() { + return true } } + return false } 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 31d62fa916e5659cf424839cedf8f063fabedaa0..f9b046a2c52814cd4e5ea38f9ea4c58c8bdb5bc4 100644 --- a/registry/etcdv3/listener.go +++ b/registry/etcdv3/listener.go @@ -18,7 +18,6 @@ package etcdv3 import ( - "context" "strings" ) @@ -39,6 +38,7 @@ type dataListener struct { listener config_center.ConfigurationListener } +// NewRegistryDataListener ... func NewRegistryDataListener(listener config_center.ConfigurationListener) *dataListener { return &dataListener{listener: listener, interestedURL: []*common.URL{}} } @@ -50,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 @@ -77,11 +77,13 @@ type configurationListener struct { events chan *config_center.ConfigChangeEvent } +// NewConfigurationListener for listening the event of etcdv3. func NewConfigurationListener(reg *etcdV3Registry) *configurationListener { // add a new waiter - reg.wg.Add(1) + 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 } @@ -89,7 +91,7 @@ func (l *configurationListener) Process(configType *config_center.ConfigChangeEv func (l *configurationListener) Next() (*registry.ServiceEvent, error) { for { select { - case <-l.registry.done: + case <-l.registry.Done(): logger.Warnf("listener's etcd client connection is broken, so etcd event listener exit now.") return nil, perrors.New("listener stopped") @@ -97,7 +99,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { logger.Infof("got etcd event %#v", e) if e.ConfigType == remoting.EventTypeDel { select { - case <-l.registry.done: + case <-l.registry.Done(): logger.Warnf("update @result{%s}. But its connection to registry is invalid", e.Value) default: } @@ -107,6 +109,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } } } + func (l *configurationListener) Close() { - l.registry.wg.Done() + l.registry.WaitGroup().Done() } diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index 842a216b94a3ae9092afba370aed154e0925c013..5f5d95aa3644fa201c10c510239a698b90b90847 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -18,7 +18,6 @@ package etcdv3 import ( - "context" "os/exec" "testing" "time" @@ -81,7 +80,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.go b/registry/etcdv3/registry.go index 68ad456ca1c531a562abb1252c696844d475de12..e1c25768119ea7d7122b9aa22a5f881db44bafd9 100644 --- a/registry/etcdv3/registry.go +++ b/registry/etcdv3/registry.go @@ -19,17 +19,13 @@ package etcdv3 import ( "fmt" - "net/url" - "os" "path" - "strconv" "strings" "sync" "time" ) import ( - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -42,72 +38,39 @@ import ( "github.com/apache/dubbo-go/remoting/etcdv3" ) -var ( - processID = "" - localIP = "" -) - const ( - Name = "etcdv3" - RegistryConnDelay = 3 + // Name module name + Name = "etcdv3" ) func init() { - processID = fmt.Sprintf("%d", os.Getpid()) - localIP, _ = gxnet.GetLocalIP() extension.SetRegistry(Name, newETCDV3Registry) } type etcdV3Registry struct { - *common.URL - birth int64 // time of file birth, seconds since Epoch; 0 if unknown - - cltLock sync.Mutex - client *etcdv3.Client - services map[string]common.URL // service name + protocol -> service config - + registry.BaseRegistry + cltLock sync.Mutex + client *etcdv3.Client listenerLock sync.Mutex listener *etcdv3.EventListener dataListener *dataListener configListener *configurationListener - - wg sync.WaitGroup // wg+done for etcd client restart - done chan struct{} } +// Client get the etcdv3 client func (r *etcdV3Registry) Client() *etcdv3.Client { return r.client } + +//SetClient set the etcdv3 client func (r *etcdV3Registry) SetClient(client *etcdv3.Client) { r.client = client } + +// func (r *etcdV3Registry) ClientLock() *sync.Mutex { return &r.cltLock } -func (r *etcdV3Registry) WaitGroup() *sync.WaitGroup { - return &r.wg -} -func (r *etcdV3Registry) GetDone() chan struct{} { - return r.done -} -func (r *etcdV3Registry) RestartCallBack() bool { - - services := []common.URL{} - for _, confIf := range r.services { - services = append(services, confIf) - } - - for _, confIf := range services { - err := r.Register(confIf) - if err != nil { - logger.Errorf("(etcdV3ProviderRegistry)register(conf{%#v}) = error{%#v}", - confIf, perrors.WithStack(err)) - return false - } - logger.Infof("success to re-register service :%v", confIf.Key()) - } - return true -} func newETCDV3Registry(url *common.URL) (registry.Registry, error) { @@ -120,12 +83,9 @@ func newETCDV3Registry(url *common.URL) (registry.Registry, error) { logger.Infof("etcd address is: %v, timeout is: %s", url.Location, timeout.String()) - r := &etcdV3Registry{ - URL: url, - birth: time.Now().UnixNano(), - done: make(chan struct{}), - services: make(map[string]common.URL), - } + r := &etcdV3Registry{} + + r.InitBaseRegistry(url, r) if err := etcdv3.ValidateClient( r, @@ -135,89 +95,37 @@ func newETCDV3Registry(url *common.URL) (registry.Registry, error) { ); err != nil { return nil, err } + r.WaitGroup().Add(1) //etcdv3 client start successful, then wg +1 - r.wg.Add(1) go etcdv3.HandleClientRestart(r) - r.listener = etcdv3.NewEventListener(r.client) - r.configListener = NewConfigurationListener(r) - r.dataListener = NewRegistryDataListener(r.configListener) + r.InitListeners() return r, nil } -func (r *etcdV3Registry) GetUrl() common.URL { - return *r.URL -} - -func (r *etcdV3Registry) IsAvailable() bool { - - select { - case <-r.done: - return false - default: - return true - } +func (r *etcdV3Registry) InitListeners() { + r.listener = etcdv3.NewEventListener(r.client) + r.configListener = NewConfigurationListener(r) + r.dataListener = NewRegistryDataListener(r.configListener) } -func (r *etcdV3Registry) Destroy() { - - if r.configListener != nil { - r.configListener.Close() - } - r.stop() +func (r *etcdV3Registry) DoRegister(root string, node string) error { + return r.client.Create(path.Join(root, node), "") } -func (r *etcdV3Registry) stop() { - - close(r.done) - - // close current client +func (r *etcdV3Registry) CloseAndNilClient() { r.client.Close() - - r.cltLock.Lock() r.client = nil - r.services = nil - r.cltLock.Unlock() } -func (r *etcdV3Registry) Register(svc common.URL) error { - - role, err := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) - if err != nil { - return perrors.WithMessage(err, "get registry role") - } - - r.cltLock.Lock() - if _, ok := r.services[svc.Key()]; ok { - r.cltLock.Unlock() - return perrors.New(fmt.Sprintf("Path{%s} has been registered", svc.Path)) - } - r.cltLock.Unlock() - - switch role { - case common.PROVIDER: - logger.Debugf("(provider register )Register(conf{%#v})", svc) - if err := r.registerProvider(svc); err != nil { - return perrors.WithMessage(err, "register provider") - } - case common.CONSUMER: - logger.Debugf("(consumer register )Register(conf{%#v})", svc) - if err := r.registerConsumer(svc); err != nil { - return perrors.WithMessage(err, "register consumer") - } - default: - return perrors.New(fmt.Sprintf("unknown role %d", role)) +func (r *etcdV3Registry) CloseListener() { + if r.configListener != nil { + r.configListener.Close() } - - r.cltLock.Lock() - r.services[svc.Key()] = svc - r.cltLock.Unlock() - return nil } -func (r *etcdV3Registry) createDirIfNotExist(k string) error { - +func (r *etcdV3Registry) CreatePath(k string) error { var tmpPath string for _, str := range strings.Split(k, "/")[1:] { tmpPath = path.Join(tmpPath, "/", str) @@ -229,85 +137,7 @@ func (r *etcdV3Registry) createDirIfNotExist(k string) error { return nil } -func (r *etcdV3Registry) registerConsumer(svc common.URL) error { - - consumersNode := fmt.Sprintf("/dubbo/%s/%s", svc.Service(), common.DubboNodes[common.CONSUMER]) - if err := r.createDirIfNotExist(consumersNode); err != nil { - logger.Errorf("etcd client create path %s: %v", consumersNode, err) - return perrors.WithMessage(err, "etcd create consumer nodes") - } - providersNode := fmt.Sprintf("/dubbo/%s/%s", svc.Service(), common.DubboNodes[common.PROVIDER]) - if err := r.createDirIfNotExist(providersNode); err != nil { - return perrors.WithMessage(err, "create provider node") - } - - params := url.Values{} - - params.Add("protocol", svc.Protocol) - - params.Add("category", (common.RoleType(common.CONSUMER)).String()) - params.Add("dubbo", "dubbogo-consumer-"+constant.Version) - - encodedURL := url.QueryEscape(fmt.Sprintf("consumer://%s%s?%s", localIP, svc.Path, params.Encode())) - dubboPath := fmt.Sprintf("/dubbo/%s/%s", svc.Service(), (common.RoleType(common.CONSUMER)).String()) - if err := r.client.Create(path.Join(dubboPath, encodedURL), ""); err != nil { - return perrors.WithMessagef(err, "create k/v in etcd (path:%s, url:%s)", dubboPath, encodedURL) - } - - return nil -} - -func (r *etcdV3Registry) registerProvider(svc common.URL) error { - - if len(svc.Path) == 0 || len(svc.Methods) == 0 { - return perrors.New(fmt.Sprintf("service path %s or service method %s", svc.Path, svc.Methods)) - } - - var ( - urlPath string - encodedURL string - dubboPath string - ) - - providersNode := fmt.Sprintf("/dubbo/%s/%s", svc.Service(), common.DubboNodes[common.PROVIDER]) - if err := r.createDirIfNotExist(providersNode); err != nil { - return perrors.WithMessage(err, "create provider node") - } - - params := url.Values{} - - svc.RangeParams(func(key, value string) bool { - params[key] = []string{value} - return true - }) - params.Add("pid", processID) - params.Add("ip", localIP) - params.Add("anyhost", "true") - params.Add("category", (common.RoleType(common.PROVIDER)).String()) - params.Add("dubbo", "dubbo-provider-golang-"+constant.Version) - params.Add("side", (common.RoleType(common.PROVIDER)).Role()) - - logger.Debugf("provider url params:%#v", params) - var host string - if len(svc.Ip) == 0 { - host = localIP + ":" + svc.Port - } else { - host = svc.Ip + ":" + svc.Port - } - - urlPath = svc.Path - - encodedURL = url.QueryEscape(fmt.Sprintf("%s://%s%s?%s", svc.Protocol, host, urlPath, params.Encode())) - dubboPath = fmt.Sprintf("/dubbo/%s/%s", svc.Service(), (common.RoleType(common.PROVIDER)).String()) - - if err := r.client.Create(path.Join(dubboPath, encodedURL), ""); err != nil { - return perrors.WithMessagef(err, "create k/v in etcd (path:%s, url:%s)", dubboPath, encodedURL) - } - - return nil -} - -func (r *etcdV3Registry) subscribe(svc *common.URL) (registry.Listener, error) { +func (r *etcdV3Registry) DoSubscribe(svc *common.URL) (registry.Listener, error) { var ( configListener *configurationListener @@ -328,12 +158,7 @@ func (r *etcdV3Registry) subscribe(svc *common.URL) (registry.Listener, error) { listener := etcdv3.NewEventListener(r.client) r.listenerLock.Lock() - // NOTICE: - // double-check the listener - // if r.listener already be assigned, discard the new value - if r.listener == nil { - r.listener = listener - } + r.listener = listener r.listenerLock.Unlock() } @@ -345,37 +170,3 @@ func (r *etcdV3Registry) subscribe(svc *common.URL) (registry.Listener, error) { return configListener, nil } - -//subscribe from registry -func (r *etcdV3Registry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) { - for { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - return - } - - listener, err := r.subscribe(url) - if err != nil { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - return - } - logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) - time.Sleep(time.Duration(RegistryConnDelay) * time.Second) - continue - } - - for { - if serviceEvent, err := listener.Next(); err != nil { - logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) - listener.Close() - return - } else { - logger.Infof("update begin, service event: %v", serviceEvent.String()) - notifyListener.Notify(serviceEvent) - } - - } - - } -} diff --git a/registry/etcdv3/registry_test.go b/registry/etcdv3/registry_test.go index 3f8c0f4cfccc2bcc68fc1e55fa69d74e9f0f8c0f..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) } @@ -46,7 +45,8 @@ func initRegistry(t *testing.T) *etcdV3Registry { } out := reg.(*etcdV3Registry) - out.client.CleanKV() + err = out.client.CleanKV() + assert.NoError(t, err) return out } @@ -54,23 +54,24 @@ 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) + assert.NoError(t, err) children, _, err := reg.client.GetChildrenKVList("/dubbo/com.ikurento.user.UserProvider/providers") if err != nil { t.Fatal(err) } - assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-2.6.0%26.*provider", children) + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*provider", children) assert.NoError(t, err) } 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 @@ -83,8 +84,9 @@ func (suite *RegistryTestSuite) TestSubscribe() { regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) reg2 := initRegistry(t) - reg2.Register(url) - listener, err := reg2.subscribe(&url) + err = reg2.Register(url) + assert.NoError(t, err) + listener, err := reg2.DoSubscribe(&url) if err != nil { t.Fatal(err) } @@ -99,10 +101,10 @@ 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.subscribe(&url) + _, err := reg.DoSubscribe(&url) if err != nil { t.Fatal(err) } @@ -119,8 +121,9 @@ 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"})) - reg.Register(url) + 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) //listener.Close() time.Sleep(1e9) diff --git a/registry/event.go b/registry/event.go index 24f5b72e8b27d4dc727e72d641d8bae3e00ff165..37d863d2162cb3b9d6a9f7eba8823286eb99441c 100644 --- a/registry/event.go +++ b/registry/event.go @@ -36,6 +36,7 @@ func init() { // service event ////////////////////////////////////////// +// ServiceEvent ... type ServiceEvent struct { Action remoting.EventType Service common.URL diff --git a/registry/mock_registry.go b/registry/mock_registry.go index 512c452e39082d619ffceae7f82d28127fbe2975..9591928eebd22bf2a99ec9dcfeb285c4519a3b90 100644 --- a/registry/mock_registry.go +++ b/registry/mock_registry.go @@ -30,11 +30,13 @@ import ( "github.com/apache/dubbo-go/common/logger" ) +// MockRegistry ... type MockRegistry struct { listener *listener destroyed *atomic.Bool } +// NewMockRegistry ... func NewMockRegistry(url *common.URL) (Registry, error) { registry := &MockRegistry{ destroyed: atomic.NewBool(false), @@ -43,17 +45,24 @@ func NewMockRegistry(url *common.URL) (Registry, error) { registry.listener = listener return registry, nil } + +// Register ... func (*MockRegistry) Register(url common.URL) error { return nil } +// Destroy ... func (r *MockRegistry) Destroy() { if r.destroyed.CAS(false, true) { } } + +// IsAvailable ... func (r *MockRegistry) IsAvailable() bool { return !r.destroyed.Load() } + +// GetUrl ... func (r *MockRegistry) GetUrl() common.URL { return common.URL{} } @@ -61,6 +70,8 @@ func (r *MockRegistry) GetUrl() common.URL { func (r *MockRegistry) subscribe(*common.URL) (Listener, error) { return r.listener, nil } + +// Subscribe ... func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) { go func() { for { @@ -81,17 +92,16 @@ func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) } for { - if serviceEvent, err := listener.Next(); err != nil { + serviceEvent, err := listener.Next() + if err != nil { listener.Close() time.Sleep(time.Duration(3) * time.Second) return - } else { - logger.Infof("update begin, service event: %v", serviceEvent.String()) - notifyListener.Notify(serviceEvent) } + logger.Infof("update begin, service event: %v", serviceEvent.String()) + notifyListener.Notify(serviceEvent) } - } }() } @@ -113,6 +123,7 @@ func (*listener) Close() { } +// MockEvent ... func (r *MockRegistry) MockEvent(event *ServiceEvent) { r.listener.listenChan <- event } diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index 25cd3d09b5711e4e7db56cd8e40f3283f3252e10..a2237dca265f25b07b19a8e1f4fe5a5f6ea9183e 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -51,6 +51,7 @@ type nacosListener struct { subscribeParam *vo.SubscribeParam } +// NewNacosListener ... func NewNacosListener(url common.URL, namingClient naming_client.INamingClient) (*nacosListener, error) { listener := &nacosListener{ namingClient: namingClient, diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index a8b9fa83fa73858064e570722341c14f974f5c9e..965e91e894ac61562bfd25c8f564f789afd6c8a1 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -47,6 +47,7 @@ var ( ) const ( + //RegistryConnDelay registry connection delay RegistryConnDelay = 3 ) @@ -209,15 +210,15 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti } for { - if serviceEvent, err := listener.Next(); err != nil { + serviceEvent, err := listener.Next() + if err != nil { logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) listener.Close() return - } else { - logger.Infof("update begin, service event: %v", serviceEvent.String()) - notifyListener.Notify(serviceEvent) } + logger.Infof("update begin, service event: %v", serviceEvent.String()) + notifyListener.Notify(serviceEvent) } } 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 534a4b945965f332e49ff343557fa20355921454..a7678ba4e2f38cfeb77f202103e03066a7efdbef 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -18,12 +18,13 @@ package protocol import ( + "context" "strings" "sync" ) import ( - "github.com/dubbogo/gost/container/gxset" + gxset "github.com/dubbogo/gost/container/set" ) import ( @@ -76,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 { @@ -84,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 { @@ -114,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", @@ -130,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() }) @@ -171,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) @@ -201,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) @@ -275,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 @@ -286,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 @@ -333,10 +341,12 @@ 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 } +// GetProtocol ... func GetProtocol() protocol.Protocol { if regProtocol != nil { return regProtocol @@ -356,10 +366,10 @@ func newWrappedInvoker(invoker protocol.Invoker, url *common.URL) *wrappedInvoke } } -func (ivk *wrappedInvoker) Invoke(invocation protocol.Invocation) protocol.Result { +func (ivk *wrappedInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { // get right url ivk.invoker.(*proxy_factory.ProxyInvoker).BaseInvoker = *protocol.NewBaseInvoker(ivk.GetUrl()) - return ivk.invoker.Invoke(invocation) + return ivk.invoker.Invoke(ctx, invocation) } type providerConfigurationListener struct { 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/registry.go b/registry/registry.go index c7279a29e1f423ca200aa2bf9390c127efcf10cb..d673864700e6ba99e8f0283247d53760b85598aa 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -21,7 +21,13 @@ import ( "github.com/apache/dubbo-go/common" ) -// Extension - Registry +/* + * -----------------------------------NOTICE--------------------------------------------- + * If there is no special case, you'd better inherit BaseRegistry and implement the + * FacadeBasedRegistry interface instead of directly implementing the Registry interface. + * -------------------------------------------------------------------------------------- + */ +// Registry Extension - Registry type Registry interface { common.Node //used for service provider calling , register services to registry @@ -38,11 +44,13 @@ type Registry interface { //mode2 : callback mode, subscribe with notify(notify listener). Subscribe(*common.URL, NotifyListener) } + +// NotifyListener ... type NotifyListener interface { Notify(*ServiceEvent) } -//Deprecated! +// Listener Deprecated! type Listener interface { Next() (*ServiceEvent, error) Close() diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index 857421f07706d6bdfec5a3ec21ba674627633458..fe8e42db9f39190e34142149a6b67c9638a84ed2 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -18,8 +18,8 @@ package zookeeper import ( - "context" "strings" + "sync" ) import ( @@ -35,27 +35,32 @@ import ( zk "github.com/apache/dubbo-go/remoting/zookeeper" ) +// RegistryDataListener ... type RegistryDataListener struct { interestedURL []*common.URL listener config_center.ConfigurationListener } +// NewRegistryDataListener ... func NewRegistryDataListener(listener config_center.ConfigurationListener) *RegistryDataListener { return &RegistryDataListener{listener: listener, interestedURL: []*common.URL{}} } + +// AddInterestedURL ... func (l *RegistryDataListener) AddInterestedURL(url *common.URL) { l.interestedURL = append(l.interestedURL, url) } +// DataChange ... 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 @@ -70,20 +75,27 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { return false } +// RegistryConfigurationListener ... type RegistryConfigurationListener struct { - client *zk.ZookeeperClient - registry *zkRegistry - events chan *config_center.ConfigChangeEvent + client *zk.ZookeeperClient + registry *zkRegistry + events chan *config_center.ConfigChangeEvent + isClosed bool + closeOnce sync.Once } +// NewRegistryConfigurationListener for listening the event of zk. func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry) *RegistryConfigurationListener { - reg.wg.Add(1) - return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)} + reg.WaitGroup().Add(1) + return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32), isClosed: false} } + +// Process ... func (l *RegistryConfigurationListener) Process(configType *config_center.ConfigChangeEvent) { l.events <- configType } +// Next ... func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { for { select { @@ -91,8 +103,8 @@ func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { logger.Warnf("listener's zk client connection is broken, so zk event listener exit now.") return nil, perrors.New("listener stopped") - case <-l.registry.done: - logger.Warnf("zk consumer register has quit, so zk event listener exit asap now.") + case <-l.registry.Done(): + logger.Warnf("zk consumer register has quit, so zk event listener exit now.") return nil, perrors.New("listener stopped") case e := <-l.events: @@ -108,8 +120,14 @@ func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { } } } + +// Close ... func (l *RegistryConfigurationListener) Close() { - l.registry.wg.Done() + // ensure that the listener will be closed at most once. + l.closeOnce.Do(func() { + l.isClosed = true + l.registry.WaitGroup().Done() + }) } func (l *RegistryConfigurationListener) valid() bool { diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go index 910d47b7e4e3d27c6f7245777cba1f46adc8e318..1a76b29a6f64e0329b289ce50218032a25f6f5cd 100644 --- a/registry/zookeeper/listener_test.go +++ b/registry/zookeeper/listener_test.go @@ -18,7 +18,6 @@ package zookeeper import ( - "context" "testing" ) @@ -34,9 +33,9 @@ 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-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-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-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"}) + 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 29ae51d44f3691807cbc74912290ba141d1f5d47..e13443d57d7dae9fb5d50b2e1c28f618780fd850 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -18,20 +18,16 @@ package zookeeper import ( - "context" "fmt" "net/url" - "os" - "strconv" "strings" "sync" "time" ) import ( - gxnet "github.com/dubbogo/gost/net" + "github.com/dubbogo/go-zookeeper/zk" perrors "github.com/pkg/errors" - "github.com/samuel/go-zookeeper/zk" ) import ( @@ -44,19 +40,11 @@ import ( ) const ( - RegistryZkClient = "zk registry" - RegistryConnDelay = 3 -) - -var ( - processID = "" - localIP = "" + // RegistryZkClient zk client name + RegistryZkClient = "zk registry" ) func init() { - processID = fmt.Sprintf("%d", os.Getpid()) - localIP, _ = gxnet.GetLocalIP() - //plugins.PluggableRegistries["zookeeper"] = newZkRegistry extension.SetRegistry("zookeeper", newZkRegistry) } @@ -65,20 +53,13 @@ func init() { ///////////////////////////////////// type zkRegistry struct { - context context.Context - *common.URL - birth int64 // time of file birth, seconds since Epoch; 0 if unknown - wg sync.WaitGroup // wg+done for zk restart - done chan struct{} - - cltLock sync.Mutex - client *zookeeper.ZookeeperClient - services map[string]common.URL // service name + protocol -> service config - + registry.BaseRegistry + client *zookeeper.ZookeeperClient listenerLock sync.Mutex listener *zookeeper.ZkEventListener dataListener *RegistryDataListener configListener *RegistryConfigurationListener + cltLock sync.Mutex //for provider zkPath map[string]int // key = protocol://ip:port/interface } @@ -88,21 +69,17 @@ func newZkRegistry(url *common.URL) (registry.Registry, error) { err error r *zkRegistry ) - r = &zkRegistry{ - URL: url, - birth: time.Now().UnixNano(), - done: make(chan struct{}), - services: make(map[string]common.URL), - zkPath: make(map[string]int), + zkPath: make(map[string]int), } + r.InitBaseRegistry(url, r) err = zookeeper.ValidateZookeeperClient(r, zookeeper.WithZkName(RegistryZkClient)) if err != nil { return nil, err } + r.WaitGroup().Add(1) //zk client start successful, then wg +1 - r.wg.Add(1) go zookeeper.HandleClientRestart(r) r.listener = zookeeper.NewZkEventListener(r.client) @@ -112,10 +89,12 @@ func newZkRegistry(url *common.URL) (registry.Registry, error) { return r, nil } +// Options ... type Options struct { client *zookeeper.ZookeeperClient } +// Option ... type Option func(*Options) func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestCluster, *zkRegistry, error) { @@ -127,246 +106,58 @@ func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestClust ) r = &zkRegistry{ - URL: url, - birth: time.Now().UnixNano(), - done: make(chan struct{}), - services: make(map[string]common.URL), - zkPath: make(map[string]int), + zkPath: make(map[string]int), } - + r.InitBaseRegistry(url, r) c, r.client, _, err = zookeeper.NewMockZookeeperClient("test", 15*time.Second, opts...) if err != nil { return nil, nil, err } - r.wg.Add(1) + r.WaitGroup().Add(1) //zk client start successful, then wg +1 go zookeeper.HandleClientRestart(r) + r.InitListeners() + return c, r, nil +} +func (r *zkRegistry) InitListeners() { r.listener = zookeeper.NewZkEventListener(r.client) r.configListener = NewRegistryConfigurationListener(r.client, r) r.dataListener = NewRegistryDataListener(r.configListener) +} - return c, r, nil +func (r *zkRegistry) CreatePath(path string) error { + return r.ZkClient().Create(path) } -func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient { - return r.client +func (r *zkRegistry) DoRegister(root string, node string) error { + return r.registerTempZookeeperNode(root, node) } -func (r *zkRegistry) SetZkClient(client *zookeeper.ZookeeperClient) { - r.client = client +func (r *zkRegistry) DoSubscribe(conf *common.URL) (registry.Listener, error) { + return r.getListener(conf) } -func (r *zkRegistry) ZkClientLock() *sync.Mutex { - return &r.cltLock +func (r *zkRegistry) CloseAndNilClient() { + r.client.Close() + r.client = nil } -func (r *zkRegistry) WaitGroup() *sync.WaitGroup { - return &r.wg +func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient { + return r.client } -func (r *zkRegistry) GetDone() chan struct{} { - return r.done +func (r *zkRegistry) SetZkClient(client *zookeeper.ZookeeperClient) { + r.client = client } -func (r *zkRegistry) GetUrl() common.URL { - return *r.URL +func (r *zkRegistry) ZkClientLock() *sync.Mutex { + return &r.cltLock } -func (r *zkRegistry) Destroy() { +func (r *zkRegistry) CloseListener() { if r.configListener != nil { r.configListener.Close() } - close(r.done) - r.wg.Wait() - r.closeRegisters() -} - -func (r *zkRegistry) RestartCallBack() bool { - - // copy r.services - services := []common.URL{} - for _, confIf := range r.services { - services = append(services, confIf) - } - - flag := true - for _, confIf := range services { - err := r.register(confIf) - if err != nil { - logger.Errorf("(ZkProviderRegistry)register(conf{%#v}) = error{%#v}", - confIf, perrors.WithStack(err)) - flag = false - break - } - logger.Infof("success to re-register service :%v", confIf.Key()) - } - return flag -} - -func (r *zkRegistry) Register(conf common.URL) error { - var ( - ok bool - err error - ) - role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) - switch role { - case common.CONSUMER: - r.cltLock.Lock() - _, ok = r.services[conf.Key()] - r.cltLock.Unlock() - if ok { - return perrors.Errorf("Path{%s} has been registered", conf.Path) - } - - err = r.register(conf) - if err != nil { - return perrors.WithStack(err) - } - - r.cltLock.Lock() - r.services[conf.Key()] = conf - r.cltLock.Unlock() - logger.Debugf("(consumerZkConsumerRegistry)Register(conf{%#v})", conf) - - case common.PROVIDER: - - // Check if the service has been registered - r.cltLock.Lock() - // Note the difference between consumer and consumerZookeeperRegistry (consumer use conf.Path). - // Because the consumer wants to provide monitoring functions for the selector, - // the provider allows multiple groups or versions of the same service to be registered. - _, ok = r.services[conf.Key()] - r.cltLock.Unlock() - if ok { - return perrors.Errorf("Path{%s} has been registered", conf.Key()) - } - - err = r.register(conf) - if err != nil { - return perrors.WithMessagef(err, "register(conf:%+v)", conf) - } - - r.cltLock.Lock() - r.services[conf.Key()] = conf - r.cltLock.Unlock() - - logger.Debugf("(ZkProviderRegistry)Register(conf{%#v})", conf) - } - - return nil -} - -func (r *zkRegistry) register(c common.URL) error { - var ( - err error - //revision string - params url.Values - rawURL string - encodedURL string - dubboPath string - //conf config.URL - ) - - err = zookeeper.ValidateZookeeperClient(r, zookeeper.WithZkName(RegistryZkClient)) - if err != nil { - return perrors.WithStack(err) - } - params = url.Values{} - - c.RangeParams(func(key, value string) bool { - params.Add(key, value) - return true - }) - - params.Add("pid", processID) - params.Add("ip", localIP) - //params.Add("timeout", fmt.Sprintf("%d", int64(r.Timeout)/1e6)) - - role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) - switch role { - - case common.PROVIDER: - - if c.Path == "" || len(c.Methods) == 0 { - return perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods) - } - // 鍏堝垱寤烘湇鍔′笅闈㈢殑provider node - dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.PROVIDER]) - r.cltLock.Lock() - err = r.client.Create(dubboPath) - r.cltLock.Unlock() - if err != nil { - logger.Errorf("zkClient.create(path{%s}) = error{%#v}", dubboPath, perrors.WithStack(err)) - return perrors.WithMessagef(err, "zkclient.Create(path:%s)", dubboPath) - } - params.Add("anyhost", "true") - - // Dubbo java consumer to start looking for the provider url,because the category does not match, - // the provider will not find, causing the consumer can not start, so we use consumers. - // DubboRole = [...]string{"consumer", "", "", "provider"} - // params.Add("category", (RoleType(PROVIDER)).Role()) - params.Add("category", (common.RoleType(common.PROVIDER)).String()) - params.Add("dubbo", "dubbo-provider-golang-"+constant.Version) - - params.Add("side", (common.RoleType(common.PROVIDER)).Role()) - - if len(c.Methods) == 0 { - params.Add("methods", strings.Join(c.Methods, ",")) - } - logger.Debugf("provider zk url params:%#v", params) - var host string - if c.Ip == "" { - host = localIP + ":" + c.Port - } else { - host = c.Ip + ":" + c.Port - } - - rawURL = fmt.Sprintf("%s://%s%s?%s", c.Protocol, host, c.Path, params.Encode()) - encodedURL = url.QueryEscape(rawURL) - - // Print your own registration service providers. - dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), (common.RoleType(common.PROVIDER)).String()) - logger.Debugf("provider path:%s, url:%s", dubboPath, rawURL) - - case common.CONSUMER: - dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.CONSUMER]) - r.cltLock.Lock() - err = r.client.Create(dubboPath) - r.cltLock.Unlock() - if err != nil { - logger.Errorf("zkClient.create(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) - return perrors.WithStack(err) - } - dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), common.DubboNodes[common.PROVIDER]) - r.cltLock.Lock() - err = r.client.Create(dubboPath) - r.cltLock.Unlock() - if err != nil { - logger.Errorf("zkClient.create(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) - return perrors.WithStack(err) - } - - params.Add("protocol", c.Protocol) - - params.Add("category", (common.RoleType(common.CONSUMER)).String()) - params.Add("dubbo", "dubbogo-consumer-"+constant.Version) - - rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode()) - encodedURL = url.QueryEscape(rawURL) - - dubboPath = fmt.Sprintf("/dubbo/%s/%s", c.Service(), (common.RoleType(common.CONSUMER)).String()) - logger.Debugf("consumer path:%s, url:%s", dubboPath, rawURL) - - default: - return perrors.Errorf("@c{%v} type is not referencer or provider", c) - } - - err = r.registerTempZookeeperNode(dubboPath, encodedURL) - - if err != nil { - return perrors.WithMessagef(err, "registerTempZookeeperNode(path:%s, url:%s)", dubboPath, rawURL) - } - return nil } func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error { @@ -396,50 +187,16 @@ func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error { return nil } -func (r *zkRegistry) subscribe(conf *common.URL) (registry.Listener, error) { - return r.getListener(conf) -} - -//subscribe from registry -func (r *zkRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) { - for { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - return - } - - listener, err := r.subscribe(url) - if err != nil { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - return - } - logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) - time.Sleep(time.Duration(RegistryConnDelay) * time.Second) - continue - } - - for { - if serviceEvent, err := listener.Next(); err != nil { - logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) - listener.Close() - return - } else { - logger.Infof("update begin, service event: %v", serviceEvent.String()) - notifyListener.Notify(serviceEvent) - } - - } - - } -} - func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListener, error) { var ( zkListener *RegistryConfigurationListener ) r.listenerLock.Lock() + if r.configListener.isClosed { + r.listenerLock.Unlock() + return nil, perrors.New("configListener already been closed") + } zkListener = r.configListener r.listenerLock.Unlock() if r.listener == nil { @@ -461,27 +218,8 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen //Interested register to dataconfig. r.dataListener.AddInterestedURL(conf) for _, v := range strings.Split(conf.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") { - go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, conf.Service()), r.dataListener) + go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, url.QueryEscape(conf.Service())), r.dataListener) } return zkListener, nil } - -func (r *zkRegistry) closeRegisters() { - r.cltLock.Lock() - defer r.cltLock.Unlock() - logger.Infof("begin to close provider zk client") - // Close the old client first to close the tmp node. - r.client.Close() - r.client = nil - r.services = nil -} - -func (r *zkRegistry) IsAvailable() bool { - select { - case <-r.done: - return false - default: - return true - } -} diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go index 841c38da7fbf1830b6f7c55809fc50d52468ef46..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,20 +34,20 @@ 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() err := reg.Register(url) children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") - assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-2.6.0%26.*.serviceid%3Dsoa.mock%26.*provider", children) + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*.serviceid%3Dsoa.mock%26.*provider", children) assert.NoError(t, err) } 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 @@ -64,7 +63,7 @@ func Test_Subscribe(t *testing.T) { _, reg2, _ := newMockZkRegistry(®url, zookeeper.WithTestCluster(ts)) reg2.Register(url) - listener, _ := reg2.subscribe(&url) + listener, _ := reg2.DoSubscribe(&url) serviceEvent, _ := listener.Next() assert.NoError(t, err) @@ -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() @@ -85,7 +84,7 @@ func Test_ConsumerDestory(t *testing.T) { assert.NoError(t, err) err = reg.Register(url) assert.NoError(t, err) - _, err = reg.subscribe(&url) + _, err = reg.DoSubscribe(&url) assert.NoError(t, err) //listener.Close() @@ -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.go b/remoting/etcdv3/client.go index 050968565387fd31871b0aa8e9969496d39f6534..ba3ea6e864923b1e70cc4a0d31ee98415807699c 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -36,16 +36,22 @@ import ( ) const ( - ConnDelay = 3 - MaxFailTimes = 15 + // ConnDelay connection dalay + ConnDelay = 3 + // MaxFailTimes max failure times + MaxFailTimes = 15 + // RegistryETCDV3Client client name RegistryETCDV3Client = "etcd registry" ) var ( + // ErrNilETCDV3Client ... ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR - ErrKVPairNotFound = perrors.New("k/v pair not found") + // ErrKVPairNotFound ... + ErrKVPairNotFound = perrors.New("k/v pair not found") ) +// Options ... type Options struct { name string endpoints []string @@ -54,30 +60,38 @@ type Options struct { heartbeat int // heartbeat second } +// Option ... type Option func(*Options) +// WithEndpoints ... func WithEndpoints(endpoints ...string) Option { return func(opt *Options) { opt.endpoints = endpoints } } + +// WithName ... func WithName(name string) Option { return func(opt *Options) { opt.name = name } } + +// WithTimeout ... func WithTimeout(timeout time.Duration) Option { return func(opt *Options) { opt.timeout = timeout } } +// WithHeartbeat ... func WithHeartbeat(heartbeat int) Option { return func(opt *Options) { opt.heartbeat = heartbeat } } +// ValidateClient ... func ValidateClient(container clientFacade, opts ...Option) error { options := &Options{ @@ -117,6 +131,7 @@ func ValidateClient(container clientFacade, opts ...Option) error { return nil } +// Client ... type Client struct { lock sync.RWMutex @@ -191,6 +206,7 @@ func (c *Client) stop() bool { return false } +// Close ... func (c *Client) Close() { if c == nil { @@ -309,6 +325,7 @@ func (c *Client) get(k string) (string, error) { return string(resp.Kvs[0].Value), nil } +// CleanKV ... func (c *Client) CleanKV() error { c.lock.RLock() @@ -408,10 +425,12 @@ func (c *Client) keepAliveKV(k string, v string) error { return nil } +// Done ... func (c *Client) Done() <-chan struct{} { return c.exit } +// Valid ... func (c *Client) Valid() bool { select { case <-c.exit: @@ -428,6 +447,7 @@ func (c *Client) Valid() bool { return true } +// Create ... func (c *Client) Create(k string, v string) error { err := c.put(k, v) @@ -437,6 +457,7 @@ func (c *Client) Create(k string, v string) error { return nil } +// Delete ... func (c *Client) Delete(k string) error { err := c.delete(k) @@ -447,6 +468,7 @@ func (c *Client) Delete(k string) error { return nil } +// RegisterTemp ... func (c *Client) RegisterTemp(basePath string, node string) (string, error) { completeKey := path.Join(basePath, node) @@ -459,6 +481,7 @@ func (c *Client) RegisterTemp(basePath string, node string) (string, error) { return completeKey, nil } +// GetChildrenKVList ... func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { kList, vList, err := c.getChildren(k) @@ -468,6 +491,7 @@ func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { return kList, vList, nil } +// Get ... func (c *Client) Get(k string) (string, error) { v, err := c.get(k) @@ -478,6 +502,7 @@ func (c *Client) Get(k string) (string, error) { return v, nil } +// Watch ... func (c *Client) Watch(k string) (clientv3.WatchChan, error) { wc, err := c.watch(k) @@ -487,6 +512,7 @@ func (c *Client) Watch(k string) (clientv3.WatchChan, error) { return wc, nil } +// WatchWithPrefix ... func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) { wc, err := c.watchWithPrefix(prefix) diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go index b46a7eb5616f1623f8914b85827adf4278e0e7af..692ae2fb66c1094300ce67283d55722d84a6044e 100644 --- a/remoting/etcdv3/client_test.go +++ b/remoting/etcdv3/client_test.go @@ -32,6 +32,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" @@ -177,6 +178,10 @@ func (suite *ClientTestSuite) TestClientDone() { }() c.Wait.Wait() + + if c.Valid() == true { + suite.T().Fatal("client should be invalid then") + } } func (suite *ClientTestSuite) TestClientCreateKV() { @@ -301,13 +306,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 { @@ -340,25 +358,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/etcdv3/facade.go b/remoting/etcdv3/facade.go index 499044b8d77d3dcd8d32b0cb70cb78f84fae8ec4..35befc85e449ec02a6377faec300aa6b46bcc8bf 100644 --- a/remoting/etcdv3/facade.go +++ b/remoting/etcdv3/facade.go @@ -38,11 +38,12 @@ type clientFacade interface { SetClient(*Client) ClientLock() *sync.Mutex WaitGroup() *sync.WaitGroup //for wait group control, etcd client listener & etcd client container - GetDone() chan struct{} //for etcd client control + Done() chan struct{} //for etcd client control RestartCallBack() bool common.Node } +// HandleClientRestart ... func HandleClientRestart(r clientFacade) { var ( @@ -54,7 +55,7 @@ func HandleClientRestart(r clientFacade) { LOOP: for { select { - case <-r.GetDone(): + case <-r.Done(): logger.Warnf("(ETCDV3ProviderRegistry)reconnectETCDV3 goroutine exit now...") break LOOP // re-register all services @@ -71,7 +72,7 @@ LOOP: failTimes = 0 for { select { - case <-r.GetDone(): + case <-r.Done(): logger.Warnf("(ETCDV3ProviderRegistry)reconnectETCDRegistry goroutine exit now...") break LOOP case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): // avoid connect frequent diff --git a/remoting/etcdv3/listener.go b/remoting/etcdv3/listener.go index 6c9905a1000561cfe394801fb66e1a2548e171d7..e69f7269879138d7579460fd059d28c297960d7e 100644 --- a/remoting/etcdv3/listener.go +++ b/remoting/etcdv3/listener.go @@ -33,6 +33,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) +// EventListener ... type EventListener struct { client *Client keyMapLock sync.Mutex @@ -40,6 +41,7 @@ type EventListener struct { wg sync.WaitGroup } +// NewEventListener ... func NewEventListener(client *Client) *EventListener { return &EventListener{ client: client, @@ -47,7 +49,7 @@ func NewEventListener(client *Client) *EventListener { } } -// Listen on a spec key +// ListenServiceNodeEvent Listen on a spec key // this method will return true when spec key deleted, // this method will return false when deep layer connection lose func (l *EventListener) ListenServiceNodeEvent(key string, listener ...remoting.DataListener) bool { @@ -134,7 +136,7 @@ func (l *EventListener) handleEvents(event *clientv3.Event, listeners ...remotin panic("unreachable") } -// Listen on a set of key with spec prefix +// ListenServiceNodeEventWithPrefix Listen on a set of key with spec prefix func (l *EventListener) ListenServiceNodeEventWithPrefix(prefix string, listener ...remoting.DataListener) { l.wg.Add(1) @@ -180,7 +182,7 @@ func timeSecondDuration(sec int) time.Duration { return time.Duration(sec) * time.Second } -// this func is invoked by etcdv3 ConsumerRegistry::Registe/ etcdv3 ConsumerRegistry::get/etcdv3 ConsumerRegistry::getListener +// ListenServiceEvent is invoked by etcdv3 ConsumerRegistry::Registe/ etcdv3 ConsumerRegistry::get/etcdv3 ConsumerRegistry::getListener // registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent // | // --------> ListenServiceNodeEvent @@ -229,6 +231,7 @@ func (l *EventListener) ListenServiceEvent(key string, listener remoting.DataLis }(key) } +// Close ... func (l *EventListener) Close() { l.wg.Wait() } diff --git a/remoting/kubernetes/client.go b/remoting/kubernetes/client.go index beb83b902e7acf1ad8dc4f51abbaf5f01b6bcbcd..e4e9f8eb8cc2fe0b403a87dcf2975913b033ace5 100644 --- a/remoting/kubernetes/client.go +++ b/remoting/kubernetes/client.go @@ -55,8 +55,7 @@ const ( ) var ( - ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist") - ErrDubboAnnotationsAlreadyExist = perrors.New("dubbo annotations already exist") + ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist") ) type Client struct { diff --git a/remoting/listener.go b/remoting/listener.go index 8d1e357d37ff92e7bf60121133998dc1745c9af8..3713ba0ccf9d98d4470741785a9490e657cf051c 100644 --- a/remoting/listener.go +++ b/remoting/listener.go @@ -21,6 +21,7 @@ import ( "fmt" ) +// DataListener ... type DataListener interface { DataChange(eventType Event) bool //bool is return for interface implement is interesting } @@ -29,11 +30,15 @@ type DataListener interface { // event type ////////////////////////////////////////// +// EventType ... type EventType int const ( + // EventTypeAdd ... EventTypeAdd = iota + // EventTypeDel ... EventTypeDel + // EventTypeUpdate ... EventTypeUpdate ) @@ -51,6 +56,7 @@ func (t EventType) String() string { // service event ////////////////////////////////////////// +// Event ... type Event struct { Path string Action EventType diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index a7fc568f567d720448d0be63c592fae5f8df9bbf..21486aab59c3f9b44c25b68d7433f864a990149a 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -25,8 +25,8 @@ import ( ) import ( + "github.com/dubbogo/go-zookeeper/zk" perrors "github.com/pkg/errors" - "github.com/samuel/go-zookeeper/zk" ) import ( @@ -35,14 +35,19 @@ import ( ) const ( - ConnDelay = 3 + // ConnDelay connection delay interval + ConnDelay = 3 + // MaxFailTimes max fail times MaxFailTimes = 15 ) var ( errNilZkClientConn = perrors.New("zookeeperclient{conn} is nil") + errNilChildren = perrors.Errorf("has none children") + errNilNode = perrors.Errorf("node does not exist") ) +// ZookeeperClient ... type ZookeeperClient struct { name string ZkAddrs []string @@ -54,6 +59,7 @@ type ZookeeperClient struct { eventRegistry map[string][]*chan struct{} } +// StateToString ... func StateToString(state zk.State) string { switch state { case zk.StateDisconnected: @@ -85,6 +91,7 @@ func StateToString(state zk.State) string { return "zookeeper unknown state" } +// Options ... type Options struct { zkName string client *ZookeeperClient @@ -92,14 +99,17 @@ type Options struct { ts *zk.TestCluster } +// Option ... type Option func(*Options) +// WithZkName ... func WithZkName(name string) Option { return func(opt *Options) { opt.zkName = name } } +// ValidateZookeeperClient ... func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { var ( err error @@ -173,12 +183,14 @@ func newZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (* return z, nil } +// WithTestCluster ... func WithTestCluster(ts *zk.TestCluster) Option { return func(opt *Options) { opt.ts = ts } } +// NewMockZookeeperClient ... func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Event, error) { var ( err error @@ -224,6 +236,7 @@ func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option) return ts, z, event, nil } +// HandleZkEvent ... func (z *ZookeeperClient) HandleZkEvent(session <-chan zk.Event) { var ( state int @@ -248,11 +261,13 @@ LOOP: logger.Warnf("zk{addr:%s} state is StateDisconnected, so close the zk client{name:%s}.", z.ZkAddrs, z.name) z.stop() z.Lock() - if z.Conn != nil { - z.Conn.Close() - z.Conn = nil - } + conn := z.Conn + z.Conn = nil z.Unlock() + if conn != nil { + conn.Close() + } + break LOOP case (int)(zk.EventNodeDataChanged), (int)(zk.EventNodeChildrenChanged): logger.Infof("zkClient{%s} get zk node changed event{path:%s}", z.name, event.Path) @@ -282,6 +297,7 @@ LOOP: } } +// RegisterEvent ... func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) { if zkPath == "" || event == nil { return @@ -290,11 +306,13 @@ func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) { z.Lock() a := z.eventRegistry[zkPath] a = append(a, event) + z.eventRegistry[zkPath] = a logger.Debugf("zkClient{%s} register event{path:%s, ptr:%p}", z.name, zkPath, event) z.Unlock() } +// UnregisterEvent ... func (z *ZookeeperClient) UnregisterEvent(zkPath string, event *chan struct{}) { if zkPath == "" { return @@ -321,6 +339,7 @@ func (z *ZookeeperClient) UnregisterEvent(zkPath string, event *chan struct{}) { } } +// Done ... func (z *ZookeeperClient) Done() <-chan struct{} { return z.exit } @@ -336,6 +355,7 @@ func (z *ZookeeperClient) stop() bool { return false } +// ZkConnValid ... func (z *ZookeeperClient) ZkConnValid() bool { select { case <-z.exit: @@ -353,6 +373,7 @@ func (z *ZookeeperClient) ZkConnValid() bool { return valid } +// Close ... func (z *ZookeeperClient) Close() { if z == nil { return @@ -361,14 +382,17 @@ func (z *ZookeeperClient) Close() { z.stop() z.Wait.Wait() z.Lock() - if z.Conn != nil { - z.Conn.Close() - z.Conn = nil - } + conn := z.Conn + z.Conn = nil z.Unlock() + if conn != nil { + conn.Close() + } + logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs) } +// Create ... func (z *ZookeeperClient) Create(basePath string) error { var ( err error @@ -380,13 +404,15 @@ func (z *ZookeeperClient) Create(basePath string) error { tmpPath = path.Join(tmpPath, "/", str) err = errNilZkClientConn z.Lock() - if z.Conn != nil { - _, err = z.Conn.Create(tmpPath, []byte(""), 0, zk.WorldACL(zk.PermAll)) - } + conn := z.Conn z.Unlock() + if conn != nil { + _, err = conn.Create(tmpPath, []byte(""), 0, zk.WorldACL(zk.PermAll)) + } + 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) @@ -397,6 +423,7 @@ func (z *ZookeeperClient) Create(basePath string) error { return nil } +// Delete ... func (z *ZookeeperClient) Delete(basePath string) error { var ( err error @@ -404,14 +431,16 @@ func (z *ZookeeperClient) Delete(basePath string) error { err = errNilZkClientConn z.Lock() - if z.Conn != nil { - err = z.Conn.Delete(basePath, -1) - } + conn := z.Conn z.Unlock() + if conn != nil { + err = conn.Delete(basePath, -1) + } return perrors.WithMessagef(err, "Delete(basePath:%s)", basePath) } +// RegisterTemp ... func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, error) { var ( err error @@ -424,10 +453,12 @@ func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, er data = []byte("") zkPath = path.Join(basePath) + "/" + node z.Lock() - if z.Conn != nil { - tmpPath, err = z.Conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) - } + conn := z.Conn z.Unlock() + if conn != nil { + tmpPath, err = conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) + } + //if err != nil && err != zk.ErrNodeExists { if err != nil { logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err)) @@ -438,6 +469,7 @@ func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, er return tmpPath, nil } +// RegisterTempSeq ... func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, error) { var ( err error @@ -446,15 +478,17 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, err = errNilZkClientConn z.Lock() - if z.Conn != nil { - tmpPath, err = z.Conn.Create( + conn := z.Conn + z.Unlock() + if conn != nil { + tmpPath, err = conn.Create( path.Join(basePath)+"/", data, zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll), ) } - z.Unlock() + logger.Debugf("zookeeperClient.RegisterTempSeq(basePath{%s}) = tempPath{%s}", basePath, tmpPath) if err != nil && err != zk.ErrNodeExists { logger.Errorf("zkClient{%s} conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)\n", @@ -466,37 +500,44 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, return tmpPath, nil } +// GetChildrenW ... func (z *ZookeeperClient) GetChildrenW(path string) ([]string, <-chan zk.Event, error) { var ( err error children []string stat *zk.Stat - event <-chan zk.Event + watcher *zk.Watcher ) err = errNilZkClientConn z.Lock() - if z.Conn != nil { - children, stat, event, err = z.Conn.ChildrenW(path) - } + conn := z.Conn z.Unlock() + if conn != nil { + children, stat, watcher, err = conn.ChildrenW(path) + } + if err != nil { + if err == zk.ErrNoChildrenForEphemerals { + return nil, nil, errNilChildren + } if err == zk.ErrNoNode { - return nil, nil, perrors.Errorf("path{%s} has none children", path) + return nil, nil, errNilNode } logger.Errorf("zk.ChildrenW(path{%s}) = error(%v)", path, err) return nil, nil, perrors.WithMessagef(err, "zk.ChildrenW(path:%s)", path) } if stat == nil { - return nil, nil, perrors.Errorf("path{%s} has none children", path) + return nil, nil, perrors.Errorf("path{%s} get stat is nil", path) } if len(children) == 0 { - return nil, nil, perrors.Errorf("path{%s} has none children", path) + return nil, nil, errNilChildren } - return children, event, nil + return children, watcher.EvtCh, nil } +// GetChildren ... func (z *ZookeeperClient) GetChildren(path string) ([]string, error) { var ( err error @@ -506,10 +547,12 @@ func (z *ZookeeperClient) GetChildren(path string) ([]string, error) { err = errNilZkClientConn z.Lock() - if z.Conn != nil { - children, stat, err = z.Conn.Children(path) - } + conn := z.Conn z.Unlock() + if conn != nil { + children, stat, err = conn.Children(path) + } + if err != nil { if err == zk.ErrNoNode { return nil, perrors.Errorf("path{%s} has none children", path) @@ -521,25 +564,28 @@ func (z *ZookeeperClient) GetChildren(path string) ([]string, error) { return nil, perrors.Errorf("path{%s} has none children", path) } if len(children) == 0 { - return nil, perrors.Errorf("path{%s} has none children", path) + return nil, errNilChildren } return children, nil } +// ExistW ... func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) { var ( - exist bool - err error - event <-chan zk.Event + exist bool + err error + watcher *zk.Watcher ) err = errNilZkClientConn z.Lock() - if z.Conn != nil { - exist, _, event, err = z.Conn.ExistsW(zkPath) - } + conn := z.Conn z.Unlock() + if conn != nil { + exist, _, watcher, err = conn.ExistsW(zkPath) + } + if err != nil { logger.Warnf("zkClient{%s}.ExistsW(path{%s}) = error{%v}.", z.name, zkPath, perrors.WithStack(err)) return nil, perrors.WithMessagef(err, "zk.ExistsW(path:%s)", zkPath) @@ -549,9 +595,10 @@ func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) { return nil, perrors.Errorf("zkClient{%s} App zk path{%s} does not exist.", z.name, zkPath) } - return event, nil + return watcher.EvtCh, nil } +// GetContent ... func (z *ZookeeperClient) GetContent(zkPath string) ([]byte, *zk.Stat, error) { return z.Conn.Get(zkPath) } diff --git a/remoting/zookeeper/client_test.go b/remoting/zookeeper/client_test.go index f1bd0c2cb38669ad968bd83efae166a4432c6e2d..cb41eb326be95470e39694fc5df233fdf073b905 100644 --- a/remoting/zookeeper/client_test.go +++ b/remoting/zookeeper/client_test.go @@ -24,7 +24,7 @@ import ( ) import ( - "github.com/samuel/go-zookeeper/zk" + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) @@ -133,3 +133,12 @@ func TestRegisterTempSeq(t *testing.T) { states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} verifyEventStateOrder(t, event, states, "event channel") } + +func Test_UnregisterEvent(t *testing.T) { + client := &ZookeeperClient{} + client.eventRegistry = make(map[string][]*chan struct{}) + array := []*chan struct{}{} + array = append(array, new(chan struct{})) + client.eventRegistry["test"] = array + client.UnregisterEvent("test", new(chan struct{})) +} diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go index cdc7ead61226906a629fdb99b6b966ada5ee5253..055db4f716a914354d1bada653fbc0a850b615b5 100644 --- a/remoting/zookeeper/facade.go +++ b/remoting/zookeeper/facade.go @@ -35,11 +35,12 @@ type zkClientFacade interface { SetZkClient(*ZookeeperClient) ZkClientLock() *sync.Mutex WaitGroup() *sync.WaitGroup //for wait group control, zk client listener & zk client container - GetDone() chan struct{} //for zk client control + Done() chan struct{} //for zk client control RestartCallBack() bool common.Node } +// HandleClientRestart ... func HandleClientRestart(r zkClientFacade) { var ( err error @@ -51,7 +52,7 @@ func HandleClientRestart(r zkClientFacade) { LOOP: for { select { - case <-r.GetDone(): + case <-r.Done(): logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") break LOOP // re-register all services @@ -67,7 +68,7 @@ LOOP: failTimes = 0 for { select { - case <-r.GetDone(): + case <-r.Done(): logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") break LOOP case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): // Prevent crazy reconnection zk. diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index 58e0d69dcfd9bf645c147f6e920e56ed5f3951eb..a41f6cd3230700332519ce1c2d3489bfcc4b6ef0 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -18,13 +18,12 @@ package zookeeper import ( - "context" "sync" "testing" "time" ) import ( - "github.com/samuel/go-zookeeper/zk" + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) import ( @@ -55,7 +54,7 @@ func (r *mockFacade) WaitGroup() *sync.WaitGroup { return &r.wg } -func (r *mockFacade) GetDone() chan struct{} { +func (r *mockFacade) Done() chan struct{} { return r.done } @@ -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 9521ea749027582c015ac998a6f6f68d350cc3bc..77aa05ee9eada327475fa5bf86c7af2c65de0ef2 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -19,21 +19,24 @@ package zookeeper import ( "path" + "strings" "sync" "time" ) import ( "github.com/dubbogo/getty" + "github.com/dubbogo/go-zookeeper/zk" perrors "github.com/pkg/errors" - "github.com/samuel/go-zookeeper/zk" ) import ( + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/remoting" ) +// ZkEventListener ... type ZkEventListener struct { client *ZookeeperClient pathMapLock sync.Mutex @@ -41,6 +44,7 @@ type ZkEventListener struct { wg sync.WaitGroup } +// NewZkEventListener ... func NewZkEventListener(client *ZookeeperClient) *ZkEventListener { return &ZkEventListener{ client: client, @@ -48,10 +52,12 @@ func NewZkEventListener(client *ZookeeperClient) *ZkEventListener { } } +// SetClient ... func (l *ZkEventListener) SetClient(client *ZookeeperClient) { l.client = client } +// ListenServiceNodeEvent ... func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool { l.wg.Add(1) defer l.wg.Done() @@ -106,8 +112,17 @@ func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, li newChildren, err := l.client.GetChildren(zkPath) if err != nil { - logger.Errorf("path{%s} child nodes changed, zk.Children() = error{%v}", zkPath, perrors.WithStack(err)) - return + if err == errNilChildren { + content, _, err := l.client.Conn.Get(zkPath) + if err != nil { + logger.Errorf("Get new node path {%v} 's content error,message is {%v}", zkPath, perrors.WithStack(err)) + } else { + listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeUpdate, Content: string(content)}) + } + + } else { + logger.Errorf("path{%s} child nodes changed, zk.Children() = error{%v}", zkPath, perrors.WithStack(err)) + } } // a node was added -- listen the new node @@ -177,7 +192,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi if MaxFailTimes <= failTimes { failTimes = MaxFailTimes } - logger.Warnf("listenDirEvent(path{%s}) = error{%v}", zkPath, err) + logger.Infof("listenDirEvent(path{%s}) = error{%v}", zkPath, err) // clear the event channel CLEAR: for { @@ -188,6 +203,11 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi } } l.client.RegisterEvent(zkPath, &event) + if err == errNilNode { + logger.Warnf("listenDirEvent(path{%s}) got errNilNode,so exit listen", zkPath) + l.client.UnregisterEvent(zkPath, &event) + return + } select { case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): l.client.UnregisterEvent(zkPath, &event) @@ -225,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 } @@ -238,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: @@ -262,55 +286,11 @@ func timeSecondDuration(sec int) time.Duration { return time.Duration(sec) * time.Second } -// this func is invoked by ZkConsumerRegistry::Register/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener +// ListenServiceEvent is invoked by ZkConsumerRegistry::Register/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener // registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent // | // --------> ListenServiceNodeEvent func (l *ZkEventListener) ListenServiceEvent(zkPath string, listener remoting.DataListener) { - var ( - err error - dubboPath string - children []string - ) - - l.pathMapLock.Lock() - _, ok := l.pathMap[zkPath] - l.pathMapLock.Unlock() - if ok { - logger.Warnf("@zkPath %s has already been listened.", zkPath) - return - } - - l.pathMapLock.Lock() - l.pathMap[zkPath] = struct{}{} - l.pathMapLock.Unlock() - - logger.Infof("listen dubbo provider path{%s} event and wait to get all provider zk nodes", zkPath) - children, err = l.client.GetChildren(zkPath) - if err != nil { - children = nil - logger.Warnf("fail to get children of zk path{%s}", zkPath) - } - - for _, c := range children { - // listen l service node - dubboPath = path.Join(zkPath, c) - content, _, err := l.client.Conn.Get(dubboPath) - if err != nil { - logger.Errorf("Get new node path {%v} 's content error,message is {%v}", dubboPath, perrors.WithStack(err)) - } - if !listener.DataChange(remoting.Event{Path: dubboPath, Action: remoting.EventTypeAdd, Content: string(content)}) { - continue - } - logger.Infof("listen dubbo service key{%s}", dubboPath) - go func(zkPath string, listener remoting.DataListener) { - if l.ListenServiceNodeEvent(zkPath) { - listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel}) - } - logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) - }(dubboPath, listener) - } - logger.Infof("listen dubbo path{%s}", zkPath) go func(zkPath string, listener remoting.DataListener) { l.listenDirEvent(zkPath, listener) @@ -322,6 +302,7 @@ func (l *ZkEventListener) valid() bool { return l.client.ZkConnValid() } +// Close ... func (l *ZkEventListener) Close() { l.wg.Wait() } diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go index a90fbad05ae787f36d38607b0a73374d874e6994..7301cd52c392b6950b3a49f78e8124eae532b083 100644 --- a/remoting/zookeeper/listener_test.go +++ b/remoting/zookeeper/listener_test.go @@ -18,12 +18,13 @@ package zookeeper import ( + "net/url" "sync" "testing" "time" ) import ( - "github.com/samuel/go-zookeeper/zk" + "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" ) import ( @@ -65,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 @@ -96,12 +98,11 @@ func TestListener(t *testing.T) { listener := NewZkEventListener(client) dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait} listener.ListenServiceEvent("/dubbo", dataListener) - + time.Sleep(1 * time.Second) _, err := client.Conn.Set("/dubbo/dubbo.properties", []byte(changedData), 1) assert.NoError(t, err) wait.Wait() assert.Equal(t, changedData, dataListener.eventList[1].Content) - client.Close() } @@ -122,3 +123,9 @@ func (m *mockDataListener) DataChange(eventType remoting.Event) bool { } return true } + +func TestZkPath(t *testing.T) { + zkPath := "io.grpc.examples.helloworld.GreeterGrpc$IGreeter" + zkPath = url.QueryEscape(zkPath) + assert.Equal(t, zkPath, "io.grpc.examples.helloworld.GreeterGrpc%24IGreeter") +}